Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f8573b3
Show temporary retry-later message for 531 broken card connection
tgolen Aug 14, 2026
26e5b2c
Handle brokenCardConnection531 in the rter violation translation
tgolen Aug 14, 2026
0f37a43
Align 531 copy across both violation surfaces
tgolen Aug 14, 2026
4456a9f
Classify 531 as a broken connection, not a pending RTER
tgolen Aug 14, 2026
d0ec9ba
Render broken-connection status on multi-expense reports for 531/530
tgolen Aug 24, 2026
e7a46c4
Address review: drop 531 from personal-card lookups and pending exclu…
tgolen Aug 24, 2026
f3f0cc6
Merge branch 'main' into tgolenViaClaude/bank-error-531-messaging
tgolen Aug 25, 2026
2729ff4
Apply Polyglot Parrot translations for RTER broken-connection copy
tgolen Aug 25, 2026
478ec76
Fix CI: add ValueOf import to es.ts and use @components/Text in test
tgolen Aug 26, 2026
1c22141
Merge remote-tracking branch 'origin/main' into tgolenViaClaude/bank-…
tgolen Aug 26, 2026
7ee335c
Apply updated Polyglot Parrot translations for RTER broken-connection…
tgolen Aug 26, 2026
ac62f89
Preserve mixed broken connection statuses
tgolen Aug 28, 2026
fc2aa54
Document broken connection suppression
tgolen Aug 28, 2026
01e5762
Merge remote-tracking branch 'origin/main' into tgolenViaClaude/bank-…
tgolen Sep 2, 2026
eaee264
fix: isolate broken connection test environment
tgolen Sep 2, 2026
41640a5
fix: preserve unsuppressible connection status
tgolen Sep 2, 2026
17be7f4
Merge remote-tracking branch 'origin/main' into tgolenViaClaude/bank-…
tgolen Sep 2, 2026
5a9e806
test: isolate broken connection copy test
tgolen Sep 2, 2026
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
1 change: 1 addition & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6703,6 +6703,7 @@ const CONST = {
RTER_VIOLATION_TYPES: {
BROKEN_CARD_CONNECTION: 'brokenCardConnection',
BROKEN_CARD_CONNECTION_530: 'brokenCardConnection530',
BROKEN_CARD_CONNECTION_531: 'brokenCardConnection531',
BROKEN_CARD_CONNECTION_REAUTH: 'brokenCardConnectionReauth',
SEVEN_DAY_HOLD: 'sevenDayHold',
},
Expand Down
7 changes: 6 additions & 1 deletion src/components/BrokenConnectionDescription.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,24 @@ function BrokenConnectionDescription({transactionID, policy, report}: BrokenConn
const {environmentURL} = useEnvironment();

const brokenConnection530Error = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530);
const brokenConnection531Error = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_531);
const brokenConnectionReauthError = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_REAUTH);
const brokenConnectionError = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION);
const isPolicyAdmin = isPolicyAdminPolicyUtils(policy);
const workspaceCompanyCardRoute = `${environmentURL}/${ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policy?.id)}`;

if (!brokenConnection530Error && !brokenConnectionReauthError && !brokenConnectionError) {
if (!brokenConnection530Error && !brokenConnection531Error && !brokenConnectionReauthError && !brokenConnectionError) {
return '';
}

if (brokenConnection530Error) {
return translate('violations.brokenConnection530Error');
}

if (brokenConnection531Error) {
return translate('violations.brokenConnection531Error');
}

const isReauth = !!brokenConnectionReauthError;
const adminErrorKey = isReauth ? 'violations.adminReauthConnectionError' : 'violations.adminBrokenConnectionError';
const memberErrorKey = isReauth ? 'violations.memberReauthConnectionError' : 'violations.memberBrokenConnectionError';
Expand Down
10 changes: 7 additions & 3 deletions src/components/MoneyReportHeaderStatusBarSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import useTransactionsAndViolationsForReport from '@hooks/useTransactionsAndViol

import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import {isProcessingReport} from '@libs/ReportUtils';
import {getUnsuppressibleBrokenConnectionTransactionID} from '@libs/TransactionUtils';

import variables from '@styles/variables';

Expand Down Expand Up @@ -33,8 +34,9 @@ function MoneyReportHeaderStatusBarSection({reportID, statusBarType, iouTransact

const [moneyRequestReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(moneyRequestReport?.policyID)}`);
const [cardList] = useOnyx(ONYXKEYS.CARD_LIST);

const {transactions: reportTransactionsMap} = useTransactionsAndViolationsForReport(moneyRequestReport?.reportID);
const {transactions: reportTransactionsMap, violations} = useTransactionsAndViolationsForReport(moneyRequestReport?.reportID);
const transactions = Object.values(reportTransactionsMap);

const expensifyIcons = useMemoizedLazyExpensifyIcons(['Hourglass', 'Box', 'Stopwatch', 'Flag', 'CreditCardHourglass', 'ReceiptScan']);
Expand Down Expand Up @@ -98,15 +100,17 @@ function MoneyReportHeaderStatusBarSection({reportID, statusBarType, iouTransact
}

if (statusBarType === CONST.REPORT.STATUS_BAR_TYPE.BROKEN_CONNECTION) {
if (!iouTransactionID) {
// A multi-expense report has no single transaction, so use one whose broken connection cannot be suppressed.
const brokenConnectionTransactionID = iouTransactionID ?? getUnsuppressibleBrokenConnectionTransactionID(transactions, violations, cardList);
if (!brokenConnectionTransactionID) {
return null;
}
return (
<MoneyRequestHeaderStatusBar
icon={getStatusIcon(expensifyIcons.Hourglass)}
description={
<BrokenConnectionDescription
transactionID={iouTransactionID}
transactionID={brokenConnectionTransactionID}
report={moneyRequestReport}
policy={policy}
/>
Expand Down
17 changes: 7 additions & 10 deletions src/components/MoneyRequestHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useTransactionViolations from '@hooks/useTransactionViolations';

import {isPersonalCard} from '@libs/CardUtils';
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types';
import type {ReportsSplitNavigatorParamList, RightModalNavigatorParamList} from '@libs/Navigation/types';
import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils';
import {isMarkAsResolvedAction} from '@libs/ReportPrimaryActionUtils';
import {isSelfDM, isSettled as isSettledReportUtils} from '@libs/ReportUtils';
import {
getBrokenConnectionViolation,
hasPendingRTERViolation as hasPendingRTERViolationTransactionUtils,
isBrokenConnectionViolation,
isDuplicate as isDuplicateTransactionUtils,
isOnHold as isOnHoldTransactionUtils,
isPending,
isScanning,
shouldSuppressBrokenConnectionStatus,
shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationTransactionUtils,
} from '@libs/TransactionUtils';

Expand Down Expand Up @@ -129,14 +129,11 @@ function MoneyRequestHeader({reportID: reportIDProp, onBackButtonPress}: MoneyRe
return {icon: getStatusIcon(icons.CreditCardHourglass), description: translate('iou.transactionPendingDescription')};
}
if (!!transaction?.transactionID && !!transactionViolations.length && shouldShowBrokenConnectionViolation) {
const brokenConnectionError = getBrokenConnectionViolation(transactionViolations);
const cardID = brokenConnectionError?.data?.cardID;
const card = cardID ? cardList?.[cardID] : undefined;

// Only suppress the status bar for a personal card the current user actually holds. A company card the
// viewer doesn't own resolves to `undefined` here (it isn't in their cardList), and must still surface
// the broken/re-auth status to admins and approvers.
if (!!card && isPersonalCard(card) && brokenConnectionError) {
const brokenConnectionViolations = transactionViolations.filter(isBrokenConnectionViolation);

// Suppress the status only when every broken connection is on a personal card the current user holds.
// A company card missing from their cardList must still surface to admins and approvers.
if (shouldSuppressBrokenConnectionStatus(brokenConnectionViolations, cardList)) {
return undefined;
}
return {
Expand Down
19 changes: 9 additions & 10 deletions src/hooks/useMoneyReportHeaderStatusBar.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import {isPersonalCard} from '@libs/CardUtils';
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import {getAllNonDeletedTransactions} from '@libs/MoneyRequestReportUtils';
import {getFilteredReportActionsForReportView, getOneTransactionThreadReportID, getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils';
import {isMarkAsResolvedAction} from '@libs/ReportPrimaryActionUtils';
import {hasOnlyHeldExpenses as hasOnlyHeldExpensesReportUtils, isSettled as isSettledReportUtils} from '@libs/ReportUtils';
import {
allHavePendingRTERViolation,
getBrokenConnectionViolation,
hasDuplicateTransactions,
isBrokenConnectionViolation,
hasReceipt,
isPayAtEndExpense as isPayAtEndExpenseTransactionUtils,
isPending,
isScanning,
shouldSuppressBrokenConnectionStatus,
shouldShowBrokenConnectionViolationForMultipleTransactions,
} from '@libs/TransactionUtils';

Expand Down Expand Up @@ -122,14 +122,13 @@ function useMoneyReportHeaderStatusBar(reportID: string | undefined, chatReportI
if (hasDuplicates) {
return CONST.REPORT.STATUS_BAR_TYPE.DUPLICATES;
}
if (!!transaction?.transactionID && !!transactionViolations.length && shouldShowBrokenConnectionViolation) {
const brokenConnectionError = getBrokenConnectionViolation(transactionViolations);
const cardID = brokenConnectionError?.data?.cardID;
const card = cardID ? cardList?.[cardID] : undefined;

// Only suppress the status bar for a personal card the current user actually holds. A company card the
// viewer doesn't own resolves to `undefined` here, and must still surface the broken/re-auth status.
if (!!card && isPersonalCard(card) && brokenConnectionError) {
if (shouldShowBrokenConnectionViolation) {
const brokenConnectionViolations = transactionViolations.length
? transactionViolations.filter(isBrokenConnectionViolation)
: (visibleTransactions?.flatMap((t) => violations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${t.transactionID}`] ?? []).filter(isBrokenConnectionViolation) ?? []);

// A report must retain a status if any violation needs an actionable or retry-later message.
if (shouldSuppressBrokenConnectionStatus(brokenConnectionViolations, cardList)) {
return undefined;
}
return CONST.REPORT.STATUS_BAR_TYPE.BROKEN_CONNECTION;
Expand Down
26 changes: 16 additions & 10 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10136,40 +10136,46 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
isMarkAsCash?: boolean,
) => {
if (rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530) {
return 'Beleg kann wegen unterbrochener Bankverbindung nicht automatisch zugeordnet werden.';
return 'Beleg kann aufgrund einer unterbrochenen Bankverbindung nicht automatisch zugeordnet werden.';
}
if (rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_531) {
return 'Beleg kann aufgrund eines vorübergehenden Bankproblems nicht automatisch zugeordnet werden. Bitte versuchen Sie es später noch einmal.';
}
if (rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_REAUTH) {
if (isPersonalCard) {
if (!connectionLink) {
return 'Beleg kann nicht automatisch zugeordnet werden, da deine Bankverbindung erneut authentifiziert werden muss.';
return 'Beleg kann nicht automatisch zugeordnet werden, da Ihre Bankverbindung erneut authentifiziert werden muss.';
}
return isMarkAsCash
? `Beleg kann nicht automatisch zugeordnet werden, da deine Bankverbindung erneut authentifiziert werden muss. Markiere ihn als Barzahlung, um ihn zu ignorieren, oder <a href="${connectionLink}">verbinde dich erneut</a>, um den Beleg zuzuordnen.`
: `Beleg kann nicht automatisch zugeordnet werden, da deine Bankverbindung erneut authentifiziert werden muss. <a href="${connectionLink}">Erneut verbinden</a>, um den Beleg zuzuordnen.`;
? `Quittung kann nicht automatisch zugeordnet werden, da Ihre Bankverbindung erneut authentifiziert werden muss. Markieren Sie sie als Barzahlung, um dies zu ignorieren, oder <a href="${connectionLink}">verbinden Sie die Bank erneut</a>, um die Quittung zuzuordnen.`
: `Beleg kann nicht automatisch zugeordnet werden, da Ihre Bankverbindung erneut authentifiziert werden muss. <a href="${connectionLink}">Erneut verbinden</a>, um den Beleg zuzuordnen.`;
}
return isAdmin
? `Bankverbindung muss erneut authentifiziert werden. <a href="${companyCardPageURL}">Erneut verbinden, um Beleg zuzuordnen</a>`
: 'Bankverbindung muss erneut authentifiziert werden. Bitte eine:n Admin bitten, die Verbindung wiederherzustellen, um den Beleg abzugleichen.';
: 'Die Bankverbindung muss erneut authentifiziert werden. Bitten Sie eine Adminperson, die Verbindung wiederherzustellen, um den Beleg abzugleichen.';
}
if (isPersonalCard && (rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION || brokenBankConnection)) {
if (!connectionLink) {
return 'Beleg kann wegen unterbrochener Bankverbindung nicht automatisch zugeordnet werden.';
return 'Beleg kann aufgrund einer unterbrochenen Bankverbindung nicht automatisch zugeordnet werden.';
}
return isMarkAsCash
? `Beleg kann wegen einer unterbrochenen Kartenverbindung nicht automatisch zugeordnet werden. Markiere ihn als Barzahlung, um ihn zu ignorieren, oder <a href="${connectionLink}">repariere die Karte</a>, um den Beleg zuzuordnen.`
: `Quittung kann aufgrund einer unterbrochenen Kartenverbindung nicht automatisch zugeordnet werden. <a href="${connectionLink}">Karte reparieren</a>, um die Quittung zuzuordnen.`;
? `Beleg kann wegen unterbrochener Kartenverbindung nicht automatisch zugeordnet werden. Markieren Sie ihn als Barzahlung, um ihn zu ignorieren, oder <a href="${connectionLink}">reparieren Sie die Karte</a>, um den Beleg zuzuordnen.`
: `Beleg kann aufgrund einer unterbrochenen Kartenverbindung nicht automatisch zugeordnet werden. <a href="${connectionLink}">Karte reparieren</a>, um den Beleg zuzuordnen.`;
}
if (brokenBankConnection || rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION) {
return isAdmin
? `Bankverbindung unterbrochen. <a href="${companyCardPageURL}">Erneut verbinden, um Beleg zuzuordnen</a>`
: 'Bankverbindung unterbrochen. Bitte eine:n Admin bitten, die Verbindung wiederherzustellen, um den Beleg abzugleichen.';
: 'Bankverbindung unterbrochen. Bitten Sie eine Adminperson, die Verbindung wiederherzustellen, um den Beleg abzugleichen.';
}
if (!isTransactionOlderThan7Days) {
return isAdmin ? `Bitte ${member} darum, es als Barzahlung zu markieren, oder warte 7 Tage und versuche es dann erneut` : 'Wartet auf Abgleich mit Kartentransaktion.';
return isAdmin
? `Bitten Sie ${member}, dies als Barzahlung zu markieren, oder warten Sie 7 Tage und versuchen Sie es erneut`
: 'Warten auf Zusammenführung mit Kartentransaktion.';
}
return '';
},
brokenConnection530Error: 'Beleg ausstehend wegen unterbrochener Bankverbindung',
brokenConnection531Error: 'Beleg kann aufgrund eines vorübergehenden Bankproblems nicht automatisch zugeordnet werden. Bitte versuchen Sie es später noch einmal.',
adminBrokenConnectionError: ({workspaceCompanyCardRoute}: {workspaceCompanyCardRoute: string}) =>
`<muted-text-label>Beleg ausstehend wegen unterbrochener Bankverbindung. Bitte in <a href="${workspaceCompanyCardRoute}">Firmenkarten</a> beheben.</muted-text-label>`,
memberBrokenConnectionError: 'Beleg ausstehend aufgrund einer unterbrochenen Bankverbindung. Bitte wende dich an eine Workspace-Admin, um das Problem zu beheben.',
Expand Down
Loading
Loading