From cba999bd6ddfded862bab51ae4715a32c282368f Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Mon, 3 Nov 2025 19:39:44 +0500 Subject: [PATCH 01/10] update: rter violation to include company card page URL --- src/libs/Violations/ViolationsUtils.ts | 4 ++-- src/types/onyx/TransactionViolation.ts | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 51d479b13726..a6a6b00e052d 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -472,7 +472,6 @@ const ViolationsUtils = { const { brokenBankConnection = false, isAdmin = false, - email, isTransactionOlderThan7Days = false, member, category, @@ -486,6 +485,7 @@ const ViolationsUtils = { rterType, message = '', errorIndexes = [], + companyCardPageURL = '', } = violation.data ?? {}; switch (violation.name) { @@ -547,10 +547,10 @@ const ViolationsUtils = { return translate('violations.rter', { brokenBankConnection, isAdmin, - email, isTransactionOlderThan7Days, member, rterType, + companyCardPageURL, }); case 'smartscanFailed': return translate('violations.smartscanFailed', {canEdit}); diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index e0e0ee79f21e..a78f4ce3391f 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -103,6 +103,9 @@ type TransactionViolationData = { /** Comment that triggered the violation */ comment?: string; + + /** URL to the company cards page for reconnecting broken bank connections */ + companyCardPageURL?: string; }; /** Model of a transaction violation */ From 09521cb3f903afd6652ce9dff80277a0a7903ec0 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Mon, 3 Nov 2025 19:40:45 +0500 Subject: [PATCH 02/10] update: copy for rter violation in all locales --- src/languages/de.ts | 6 +++--- src/languages/en.ts | 6 ++++-- src/languages/es.ts | 6 +++--- src/languages/fr.ts | 6 +++--- src/languages/it.ts | 6 +++--- src/languages/ja.ts | 6 ++++-- src/languages/nl.ts | 6 +++--- src/languages/params.ts | 2 +- src/languages/pl.ts | 6 +++--- src/languages/pt-BR.ts | 6 +++--- src/languages/zh-hans.ts | 4 ++-- 11 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/languages/de.ts b/src/languages/de.ts index 943762000a90..24353516c272 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -6822,14 +6822,14 @@ ${amount} für ${merchant} - ${date}`, }, customRules: ({message}: ViolationsCustomRulesParams) => message, reviewRequired: 'Überprüfung erforderlich', - rter: ({brokenBankConnection, email, isAdmin, isTransactionOlderThan7Days, member, rterType}: ViolationsRterParams) => { + rter: ({brokenBankConnection, isAdmin, isTransactionOlderThan7Days, member, rterType, companyCardPageURL}: ViolationsRterParams) => { if (rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530) { return 'Kassenbon kann aufgrund einer unterbrochenen Bankverbindung nicht automatisch zugeordnet werden.'; } if (brokenBankConnection || rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION) { return isAdmin - ? `Kassenbon kann aufgrund einer unterbrochenen Bankverbindung, die ${email} beheben muss, nicht automatisch zugeordnet werden.` - : 'Kassenbon kann aufgrund einer unterbrochenen Bankverbindung nicht automatisch zugeordnet werden.'; + ? `Bankverbindung unterbrochen. Jetzt wieder verbinden, um den Beleg zuzuordnen` + : 'Bankverbindung unterbrochen. Bitte einen Admin bitten, sie wieder zu verbinden, um den Beleg zuzuordnen.'; } if (!isTransactionOlderThan7Days) { return isAdmin diff --git a/src/languages/en.ts b/src/languages/en.ts index 0678c58f4565..b4d0dcab7bfd 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -6779,12 +6779,14 @@ const translations = { }, customRules: ({message}: ViolationsCustomRulesParams) => message, reviewRequired: 'Review required', - rter: ({brokenBankConnection, email, isAdmin, isTransactionOlderThan7Days, member, rterType}: ViolationsRterParams) => { + rter: ({brokenBankConnection, isAdmin, isTransactionOlderThan7Days, member, rterType, companyCardPageURL}: ViolationsRterParams) => { if (rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530) { return "Can't auto-match receipt due to broken bank connection"; } if (brokenBankConnection || rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION) { - return isAdmin ? `Can't auto-match receipt due to broken bank connection which ${email} needs to fix` : "Can't auto-match receipt due to broken bank connection"; + return isAdmin + ? `Bank connection broken. Reconnect to match receipt` + : 'Bank connection broken. Ask an admin to reconnect to match receipt.'; } if (!isTransactionOlderThan7Days) { return isAdmin ? `Ask ${member} to mark as a cash or wait 7 days and try again` : 'Awaiting merge with card transaction.'; diff --git a/src/languages/es.ts b/src/languages/es.ts index 49079d36b4cf..c1244846796a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -7269,14 +7269,14 @@ ${amount} para ${merchant} - ${date}`, }, customRules: ({message}: ViolationsCustomRulesParams) => message, reviewRequired: 'Revisión requerida', - rter: ({brokenBankConnection, isAdmin, email, isTransactionOlderThan7Days, member, rterType}: ViolationsRterParams) => { + rter: ({brokenBankConnection, isAdmin, isTransactionOlderThan7Days, member, rterType, companyCardPageURL}: ViolationsRterParams) => { if (rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530) { return 'No se puede emparejar automáticamente el recibo debido a una conexión bancaria interrumpida.'; } if (brokenBankConnection || rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION) { return isAdmin - ? `No se puede adjuntar recibo debido a un problema con la conexión a tu banco que ${email} necesita arreglar` - : 'No se puede adjuntar recibo debido a un problema con la conexión a tu banco'; + ? `Conexión bancaria interrumpida. Vuelve a conectarte para emparejar el recibo` + : 'Conexión bancaria interrumpida. Pide a un administrador que la vuelva a conectar para emparejar el recibo.'; } if (!isTransactionOlderThan7Days) { return isAdmin diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 715dc45ae6f4..f401c4e6cb1e 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -6827,14 +6827,14 @@ ${amount} pour ${merchant} - ${date}`, }, customRules: ({message}: ViolationsCustomRulesParams) => message, reviewRequired: 'Examen requis', - rter: ({brokenBankConnection, email, isAdmin, isTransactionOlderThan7Days, member, rterType}: ViolationsRterParams) => { + rter: ({brokenBankConnection, isAdmin, isTransactionOlderThan7Days, member, rterType, companyCardPageURL}: ViolationsRterParams) => { if (rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530) { return "Impossible de faire correspondre automatiquement le reçu en raison d'une connexion bancaire défectueuse."; } if (brokenBankConnection || rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION) { return isAdmin - ? `Impossible de faire correspondre automatiquement le reçu en raison d'une connexion bancaire défectueuse que ${email} doit corriger.` - : "Impossible de faire correspondre automatiquement le reçu en raison d'une connexion bancaire défectueuse."; + ? `Connexion bancaire interrompue. Reconnecter pour associer le reçu` + : 'Connexion bancaire interrompue. Demandez à un administrateur de la reconnecter pour associer le reçu.'; } if (!isTransactionOlderThan7Days) { return isAdmin ? `Demandez à ${member} de marquer comme espèce ou attendez 7 jours et réessayez` : 'En attente de fusion avec la transaction par carte.'; diff --git a/src/languages/it.ts b/src/languages/it.ts index 7b75125a636e..650d0b4a5b07 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -6833,14 +6833,14 @@ ${amount} per ${merchant} - ${date}`, }, customRules: ({message}: ViolationsCustomRulesParams) => message, reviewRequired: 'Revisione richiesta', - rter: ({brokenBankConnection, email, isAdmin, isTransactionOlderThan7Days, member, rterType}: ViolationsRterParams) => { + rter: ({brokenBankConnection, isAdmin, isTransactionOlderThan7Days, member, rterType, companyCardPageURL}: ViolationsRterParams) => { if (rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530) { return 'Impossibile associare automaticamente la ricevuta a causa di una connessione bancaria interrotta.'; } if (brokenBankConnection || rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION) { return isAdmin - ? `Impossibile abbinare automaticamente la ricevuta a causa di una connessione bancaria interrotta che ${email} deve risolvere.` - : 'Impossibile associare automaticamente la ricevuta a causa di una connessione bancaria interrotta.'; + ? `Connessione bancaria interrotta. Riconnetti per abbinarla alla ricevuta` + : 'Connessione bancaria interrotta. Chiedi a un amministratore di riconnetterla per abbinare la ricevuta.'; } if (!isTransactionOlderThan7Days) { return isAdmin ? `Chiedi a ${member} di contrassegnarlo come contante o attendi 7 giorni e riprova` : 'In attesa di unione con la transazione della carta.'; diff --git a/src/languages/ja.ts b/src/languages/ja.ts index cc792be36cc3..9a100d9e4f7c 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -6767,12 +6767,14 @@ ${date} - ${merchant}に${amount}`, }, customRules: ({message}: ViolationsCustomRulesParams) => message, reviewRequired: 'レビューが必要です', - rter: ({brokenBankConnection, email, isAdmin, isTransactionOlderThan7Days, member, rterType}: ViolationsRterParams) => { + rter: ({brokenBankConnection, isAdmin, isTransactionOlderThan7Days, member, rterType, companyCardPageURL}: ViolationsRterParams) => { if (rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530) { return '銀行接続が切れているため、領収書を自動照合できません。'; } if (brokenBankConnection || rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION) { - return isAdmin ? `${email}が修正する必要がある銀行接続の問題のため、領収書を自動マッチングできません。` : '銀行接続が切れているため、領収書を自動照合できません。'; + return isAdmin + ? `銀行接続が切断されています。再接続して領収書を照合してください` + : '銀行接続が切断されています。領収書を照合するには管理者に再接続を依頼してください。'; } if (!isTransactionOlderThan7Days) { return isAdmin ? `${member}に現金としてマークするように依頼するか、7日間待って再試行してください。` : 'カード取引とのマージを待機中。'; diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 53b3e9921439..646a39004094 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -6818,14 +6818,14 @@ ${amount} voor ${merchant} - ${date}`, }, customRules: ({message}: ViolationsCustomRulesParams) => message, reviewRequired: 'Beoordeling vereist', - rter: ({brokenBankConnection, email, isAdmin, isTransactionOlderThan7Days, member, rterType}: ViolationsRterParams) => { + rter: ({brokenBankConnection, isAdmin, isTransactionOlderThan7Days, member, rterType, companyCardPageURL}: ViolationsRterParams) => { if (rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530) { return 'Kan bon niet automatisch koppelen vanwege verbroken bankverbinding.'; } if (brokenBankConnection || rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION) { return isAdmin - ? `Kan ontvangst niet automatisch koppelen vanwege een verbroken bankverbinding die ${email} moet herstellen.` - : 'Kan bon niet automatisch koppelen vanwege verbroken bankverbinding.'; + ? `Bankverbinding verbroken. Opnieuw verbinden om bon te koppelen` + : 'Bankverbinding verbroken. Vraag een beheerder om opnieuw te verbinden om de bon te koppelen.'; } if (!isTransactionOlderThan7Days) { return isAdmin ? `Vraag ${member} om het als contant te markeren of wacht 7 dagen en probeer het opnieuw.` : 'In afwachting van samenvoeging met kaarttransactie.'; diff --git a/src/languages/params.ts b/src/languages/params.ts index 6ee4cffa34ff..f63608742253 100644 --- a/src/languages/params.ts +++ b/src/languages/params.ts @@ -326,10 +326,10 @@ type ViolationsCustomRulesParams = {message: string}; type ViolationsRterParams = { brokenBankConnection: boolean; isAdmin: boolean; - email?: string; isTransactionOlderThan7Days: boolean; member?: string; rterType?: ValueOf; + companyCardPageURL: string; }; type ViolationsTagOutOfPolicyParams = {tagName?: string} | undefined; diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 40dda5d0bd6d..10be8808f6eb 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -6805,14 +6805,14 @@ ${amount} dla ${merchant} - ${date}`, }, customRules: ({message}: ViolationsCustomRulesParams) => message, reviewRequired: 'Wymagana recenzja', - rter: ({brokenBankConnection, email, isAdmin, isTransactionOlderThan7Days, member, rterType}: ViolationsRterParams) => { + rter: ({brokenBankConnection, isAdmin, isTransactionOlderThan7Days, member, rterType, companyCardPageURL}: ViolationsRterParams) => { if (rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530) { return 'Nie można automatycznie dopasować paragonu z powodu przerwanego połączenia z bankiem.'; } if (brokenBankConnection || rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION) { return isAdmin - ? `Nie można automatycznie dopasować paragonu z powodu zerwanego połączenia z bankiem, które ${email} musi naprawić.` - : 'Nie można automatycznie dopasować paragonu z powodu przerwanego połączenia z bankiem.'; + ? `Połączenie bankowe zostało przerwane. Połącz ponownie, aby dopasować paragon` + : 'Połączenie bankowe zostało przerwane. Poproś administratora o ponowne połączenie, aby dopasować paragon.'; } if (!isTransactionOlderThan7Days) { return isAdmin ? `Poproś ${member}, aby oznaczył jako gotówkę lub poczekaj 7 dni i spróbuj ponownie.` : 'Oczekiwanie na połączenie z transakcją kartową.'; diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 67cc651980fb..e7ff64375175 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -6813,14 +6813,14 @@ ${amount} para ${merchant} - ${date}`, }, customRules: ({message}: ViolationsCustomRulesParams) => message, reviewRequired: 'Revisão necessária', - rter: ({brokenBankConnection, email, isAdmin, isTransactionOlderThan7Days, member, rterType}: ViolationsRterParams) => { + rter: ({brokenBankConnection, isAdmin, isTransactionOlderThan7Days, member, rterType, companyCardPageURL}: ViolationsRterParams) => { if (rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530) { return 'Não é possível associar automaticamente o recibo devido a uma conexão bancária interrompida.'; } if (brokenBankConnection || rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION) { return isAdmin - ? `Não é possível associar automaticamente o recibo devido a uma conexão bancária interrompida que ${email} precisa corrigir.` - : 'Não é possível associar automaticamente o recibo devido a uma conexão bancária interrompida.'; + ? `Conexão bancária interrompida. Reconectar para associar o recibo` + : 'Conexão bancária interrompida. Peça a um administrador para reconectar e associar o recibo.'; } if (!isTransactionOlderThan7Days) { return isAdmin ? `Peça a ${member} para marcar como dinheiro ou espere 7 dias e tente novamente.` : 'Aguardando a fusão com a transação do cartão.'; diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 73f88ff1671a..2bcca252b07e 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -6673,12 +6673,12 @@ ${merchant}的${amount} - ${date}`, }, customRules: ({message}: ViolationsCustomRulesParams) => message, reviewRequired: '需要审核', - rter: ({brokenBankConnection, email, isAdmin, isTransactionOlderThan7Days, member, rterType}: ViolationsRterParams) => { + rter: ({brokenBankConnection, isAdmin, isTransactionOlderThan7Days, member, rterType, companyCardPageURL}: ViolationsRterParams) => { if (rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530) { return '由于银行连接中断,无法自动匹配收据。'; } if (brokenBankConnection || rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION) { - return isAdmin ? `由于银行连接中断,无法自动匹配收据,需要${email}进行修复。` : '由于需要修复的银行连接中断,无法自动匹配收据。'; + return isAdmin ? `银行连接已中断。重新连接以匹配收据` : '银行连接已中断。请联系管理员重新连接以匹配收据。'; } if (!isTransactionOlderThan7Days) { return isAdmin ? `请${member}标记为现金,或等待7天后再试一次。` : '正在等待与卡交易合并。'; From 7db803d1e209cf08dc0b6fc04d40ad8e343d6f21 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Mon, 3 Nov 2025 20:30:41 +0500 Subject: [PATCH 03/10] update unit test to include companyCardPageURL --- src/languages/params.ts | 2 +- src/libs/DebugUtils.ts | 1 + tests/unit/ViolationUtilsTest.ts | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/languages/params.ts b/src/languages/params.ts index f63608742253..752a7f7d81ba 100644 --- a/src/languages/params.ts +++ b/src/languages/params.ts @@ -329,7 +329,7 @@ type ViolationsRterParams = { isTransactionOlderThan7Days: boolean; member?: string; rterType?: ValueOf; - companyCardPageURL: string; + companyCardPageURL?: string; }; type ViolationsTagOutOfPolicyParams = {tagName?: string} | undefined; diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index fc5139ec96fc..add2bc7c8ca4 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -1279,6 +1279,7 @@ function validateTransactionViolationDraftProperty(key: keyof TransactionViolati field: 'string', prohibitedExpenseRule: 'string', comment: 'string', + companyCardPageURL: 'string', }); case 'showInReview': return validateBoolean(value); diff --git a/tests/unit/ViolationUtilsTest.ts b/tests/unit/ViolationUtilsTest.ts index b5617e890864..6185c2fa84cd 100644 --- a/tests/unit/ViolationUtilsTest.ts +++ b/tests/unit/ViolationUtilsTest.ts @@ -658,11 +658,14 @@ const brokenCardConnection530Violation: TransactionViolation = { describe('getViolationTranslation', () => { it('should return the correct message for broken card connection violation', () => { + const testPolicyID = 'test-policy-123'; + const companyCardPageURL = `workspaces/${testPolicyID}/company-cards`; const brokenCardConnectionViolationExpected = translateLocal('violations.rter', { brokenBankConnection: true, isAdmin: true, rterType: CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION, isTransactionOlderThan7Days: false, + companyCardPageURL, }); expect(ViolationsUtils.getViolationTranslation(brokenCardConnectionViolation, translateLocal)).toBe(brokenCardConnectionViolationExpected); const brokenCardConnection530ViolationExpected = translateLocal('violations.rter', { @@ -670,6 +673,7 @@ describe('getViolationTranslation', () => { isAdmin: false, rterType: CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530, isTransactionOlderThan7Days: false, + companyCardPageURL, }); expect(ViolationsUtils.getViolationTranslation(brokenCardConnection530Violation, translateLocal)).toBe(brokenCardConnection530ViolationExpected); }); From e19a99f39eae9891f4397ad2590b41480fe73868 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Wed, 5 Nov 2025 21:16:16 +0500 Subject: [PATCH 04/10] pass companyCardPageURL as param --- src/components/TransactionItemRow/TransactionItemRowRBR.tsx | 5 +++++ src/libs/DebugUtils.ts | 1 - src/libs/Violations/ViolationsUtils.ts | 6 +++--- src/types/onyx/TransactionViolation.ts | 3 --- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/TransactionItemRow/TransactionItemRowRBR.tsx b/src/components/TransactionItemRow/TransactionItemRowRBR.tsx index ae0fc95984ed..0e8b99d29636 100644 --- a/src/components/TransactionItemRow/TransactionItemRowRBR.tsx +++ b/src/components/TransactionItemRow/TransactionItemRowRBR.tsx @@ -4,6 +4,7 @@ import {View} from 'react-native'; import Icon from '@components/Icon'; import {DotIndicator} from '@components/Icon/Expensicons'; import RenderHTML from '@components/RenderHTML'; +import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useTheme from '@hooks/useTheme'; @@ -13,6 +14,7 @@ import {isSettled} from '@libs/ReportUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type {Report, TransactionViolation} from '@src/types/onyx'; import type Transaction from '@src/types/onyx/Transaction'; @@ -37,9 +39,11 @@ function TransactionItemRowRBR({transaction, violations, report, containerStyles const styles = useThemeStyles(); const {translate} = useLocalize(); const theme = useTheme(); + const {environmentURL} = useEnvironment(); const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transaction.reportID}`, { canBeMissing: true, }); + const companyCardPageURL = `${environmentURL}/${ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(report?.policyID)}`; const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${report?.policyID}`, {canBeMissing: true}); const transactionThreadId = reportActions ? getIOUActionForTransactionID(Object.values(reportActions ?? {}), transaction.transactionID)?.childReportID : undefined; const [transactionThreadActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadId}`, { @@ -53,6 +57,7 @@ function TransactionItemRowRBR({transaction, violations, report, containerStyles missingFieldError, Object.values(transactionThreadActions ?? {}), policyTags, + companyCardPageURL, ); return ( diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index 591c0d47fab0..80830b0579eb 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -1277,7 +1277,6 @@ function validateTransactionViolationDraftProperty(key: keyof TransactionViolati field: 'string', prohibitedExpenseRule: 'string', comment: 'string', - companyCardPageURL: 'string', }); case 'showInReview': return validateBoolean(value); diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index a6a6b00e052d..a806d04e8794 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -468,7 +468,7 @@ const ViolationsUtils = { * possible values could be either translation keys that resolve to strings or translation keys that resolve to * functions. */ - getViolationTranslation(violation: TransactionViolation, translate: LocaleContextProps['translate'], canEdit = true, tags?: PolicyTagLists): string { + getViolationTranslation(violation: TransactionViolation, translate: LocaleContextProps['translate'], canEdit = true, tags?: PolicyTagLists, companyCardPageURL?: string): string { const { brokenBankConnection = false, isAdmin = false, @@ -485,7 +485,6 @@ const ViolationsUtils = { rterType, message = '', errorIndexes = [], - companyCardPageURL = '', } = violation.data ?? {}; switch (violation.name) { @@ -595,6 +594,7 @@ const ViolationsUtils = { missingFieldError?: string, transactionThreadActions?: ReportAction[], tags?: PolicyTagLists, + companyCardPageURL?: string, ): string { const errorMessages = extractErrorMessages(transaction?.errors ?? {}, transactionThreadActions?.filter((e) => !!e.errors) ?? [], translate); @@ -604,7 +604,7 @@ const ViolationsUtils = { // Some violations end with a period already so lets make sure the connected messages have only single period between them // and end with a single dot. ...transactionViolations.map((violation) => { - const message = ViolationsUtils.getViolationTranslation(violation, translate, true, tags); + const message = ViolationsUtils.getViolationTranslation(violation, translate, true, tags, companyCardPageURL); if (!message) { return; } diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index a78f4ce3391f..e0e0ee79f21e 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -103,9 +103,6 @@ type TransactionViolationData = { /** Comment that triggered the violation */ comment?: string; - - /** URL to the company cards page for reconnecting broken bank connections */ - companyCardPageURL?: string; }; /** Model of a transaction violation */ From 95af61b54650bbe0b3eb007852d05e0834caad08 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Thu, 6 Nov 2025 17:13:49 +0500 Subject: [PATCH 05/10] fix: pass companyCardPageURL as param to getViolationTranslation --- .../ReportActionItem/MoneyRequestReceiptView.tsx | 5 ++++- .../ReportActionItem/MoneyRequestView.tsx | 6 +++++- .../TransactionPreviewContent.tsx | 6 +++++- src/components/ViolationMessages.tsx | 13 ++++++++++--- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index 8878fcd57b2b..857d1e63001d 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -8,6 +8,7 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ReceiptAudit, {ReceiptAuditMessages} from '@components/ReceiptAudit'; import ReceiptEmptyState from '@components/ReceiptEmptyState'; import useActiveRoute from '@hooks/useActiveRoute'; +import useEnvironment from '@hooks/useEnvironment'; import useGetIOUReportFromReportAction from '@hooks/useGetIOUReportFromReportAction'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; @@ -97,6 +98,7 @@ function MoneyRequestReceiptView({ }: MoneyRequestReceiptViewProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); + const {environmentURL} = useEnvironment(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const {getReportRHPActiveRoute} = useActiveRoute(); const parentReportID = report?.parentReportID; @@ -137,6 +139,7 @@ function MoneyRequestReceiptView({ const isReportArchived = useReportIsArchived(report?.reportID); const isEditable = !!canUserPerformWriteActionReportUtils(report, isReportArchived) && !readonly; const canEdit = isMoneyRequestAction(parentReportAction) && canEditMoneyRequest(parentReportAction, isChatReportArchived, moneyRequestReport, policy, transaction) && isEditable; + const companyCardPageURL = `${environmentURL}/${ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(report?.policyID)}`; const canEditReceipt = isEditable && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.RECEIPT, undefined, isChatReportArchived); @@ -171,7 +174,7 @@ function MoneyRequestReceiptView({ const isReceiptFieldViolation = receiptFieldViolationNames.includes(violation.name); const isReceiptImageViolation = receiptImageViolationNames.includes(violation.name); if (isReceiptFieldViolation || isReceiptImageViolation) { - const violationMessage = ViolationsUtils.getViolationTranslation(violation, translate, canEdit); + const violationMessage = ViolationsUtils.getViolationTranslation(violation, translate, canEdit, undefined, companyCardPageURL); allViolations.push(violationMessage); if (isReceiptImageViolation) { imageViolations.push(violationMessage); diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 781089d1bad1..8f22eadb62de 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -14,6 +14,7 @@ import Text from '@components/Text'; import ViolationMessages from '@components/ViolationMessages'; import {WideRHPContext} from '@components/WideRHPContextProvider'; import useActiveRoute from '@hooks/useActiveRoute'; +import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; @@ -129,6 +130,7 @@ function MoneyRequestView({ const theme = useTheme(); const StyleUtils = useStyleUtils(); const {isOffline} = useNetwork(); + const {environmentURL} = useEnvironment(); const {translate, toLocaleDigit} = useLocalize(); const {getReportRHPActiveRoute} = useActiveRoute(); const [lastVisitedPath] = useOnyx(ONYXKEYS.LAST_VISITED_PATH, {canBeMissing: true}); @@ -246,6 +248,7 @@ function MoneyRequestView({ const isReportArchived = useReportIsArchived(report?.reportID); const isEditable = !!canUserPerformWriteActionReportUtils(report, isReportArchived) && !readonly; const canEdit = isMoneyRequestAction(parentReportAction) && canEditMoneyRequest(parentReportAction, isChatReportArchived, moneyRequestReport, policy, transaction) && isEditable; + const companyCardPageURL = `${environmentURL}/${ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(report?.policyID)}`; const {isExpenseSplit} = getOriginalTransactionWithSplitInfo(transaction); const isSplitAvailable = moneyRequestReport && transaction && isSplitAction(moneyRequestReport, [transaction], policy); @@ -417,7 +420,7 @@ function MoneyRequestView({ const firstViolation = violations.at(0); if (firstViolation) { - return ViolationsUtils.getViolationTranslation(firstViolation, translate, canEdit); + return ViolationsUtils.getViolationTranslation(firstViolation, translate, canEdit, undefined, companyCardPageURL); } } @@ -901,6 +904,7 @@ function MoneyRequestView({ textStyle={[styles.ph0]} isLast canEdit={canEdit} + companyCardPageURL={companyCardPageURL} /> )} diff --git a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx index 203e75adc811..96faf82f92d1 100644 --- a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx +++ b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx @@ -10,6 +10,7 @@ import ReportActionItemImages from '@components/ReportActionItem/ReportActionIte import UserInfoCellsWithArrow from '@components/SelectionListWithSections/Search/UserInfoCellsWithArrow'; import Text from '@components/Text'; import TransactionPreviewSkeletonView from '@components/TransactionPreviewSkeletonView'; +import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useReportIsArchived from '@hooks/useReportIsArchived'; @@ -33,6 +34,7 @@ import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type {TransactionPreviewContentProps} from './types'; function TransactionPreviewContent({ @@ -61,6 +63,7 @@ function TransactionPreviewContent({ const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); + const {environmentURL} = useEnvironment(); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`, {canBeMissing: true}); const isParentPolicyExpenseChat = isPolicyExpenseChat(chatReport); @@ -103,7 +106,8 @@ function TransactionPreviewContent({ const firstViolation = violations.at(0); const isIOUActionType = isMoneyRequestAction(action); const canEdit = isIOUActionType && canEditMoneyRequest(action, isChatReportArchived, report, policy, transaction); - const violationMessage = firstViolation ? ViolationsUtils.getViolationTranslation(firstViolation, translate, canEdit) : undefined; + const companyCardPageURL = `${environmentURL}/${ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(report?.policyID)}`; + const violationMessage = firstViolation ? ViolationsUtils.getViolationTranslation(firstViolation, translate, canEdit, undefined, companyCardPageURL) : undefined; const previewText = useMemo( () => diff --git a/src/components/ViolationMessages.tsx b/src/components/ViolationMessages.tsx index ab0543f58011..48c73890e825 100644 --- a/src/components/ViolationMessages.tsx +++ b/src/components/ViolationMessages.tsx @@ -7,13 +7,20 @@ import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import type {TransactionViolation} from '@src/types/onyx'; import Text from './Text'; -type ViolationMessagesProps = {violations: TransactionViolation[]; isLast?: boolean; containerStyle?: StyleProp; textStyle?: StyleProp; canEdit: boolean}; +type ViolationMessagesProps = { + violations: TransactionViolation[]; + isLast?: boolean; + containerStyle?: StyleProp; + textStyle?: StyleProp; + canEdit: boolean; + companyCardPageURL?: string; +}; -export default function ViolationMessages({violations, isLast, containerStyle, textStyle, canEdit}: ViolationMessagesProps) { +export default function ViolationMessages({violations, isLast, containerStyle, textStyle, canEdit, companyCardPageURL}: ViolationMessagesProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const violationMessages = useMemo( - () => violations.map((violation) => [violation.name, ViolationsUtils.getViolationTranslation(violation, translate, canEdit)]), + () => violations.map((violation) => [violation.name, ViolationsUtils.getViolationTranslation(violation, translate, canEdit, undefined, companyCardPageURL)]), [canEdit, translate, violations], ); From 473b0697d623079d74e894468b69ed4ccda5ee79 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Thu, 6 Nov 2025 17:58:30 +0500 Subject: [PATCH 06/10] fix lint --- src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 2 +- src/components/ReportActionItem/MoneyRequestView.tsx | 1 + src/components/ViolationMessages.tsx | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index fc1337519343..179877e70937 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -181,7 +181,7 @@ function MoneyRequestReceiptView({ } } return [imageViolations, allViolations]; - }, [transactionViolations, translate, canEdit]); + }, [transactionViolations, translate, canEdit, companyCardPageURL]); const receiptRequiredViolation = transactionViolations?.some((violation) => violation.name === CONST.VIOLATIONS.RECEIPT_REQUIRED); const customRulesViolation = transactionViolations?.some((violation) => violation.name === CONST.VIOLATIONS.CUSTOM_RULES); diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index dffd5c08b60d..b2e9a81567f0 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -440,6 +440,7 @@ function MoneyRequestView({ canEditMerchant, canEdit, isCustomUnitOutOfPolicy, + MoneyRequestView, ], ); diff --git a/src/components/ViolationMessages.tsx b/src/components/ViolationMessages.tsx index 48c73890e825..77b17dd44fad 100644 --- a/src/components/ViolationMessages.tsx +++ b/src/components/ViolationMessages.tsx @@ -21,7 +21,7 @@ export default function ViolationMessages({violations, isLast, containerStyle, t const {translate} = useLocalize(); const violationMessages = useMemo( () => violations.map((violation) => [violation.name, ViolationsUtils.getViolationTranslation(violation, translate, canEdit, undefined, companyCardPageURL)]), - [canEdit, translate, violations], + [canEdit, translate, violations, companyCardPageURL], ); return ( From 800dfe43affad4fb8ddf281187cbb847532e618d Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Fri, 7 Nov 2025 09:41:09 +0500 Subject: [PATCH 07/10] fix lint --- src/components/ReportActionItem/MoneyRequestView.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 72a1663ebaf7..f76ab5e17310 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -14,8 +14,8 @@ import Text from '@components/Text'; import ViolationMessages from '@components/ViolationMessages'; import {WideRHPContext} from '@components/WideRHPContextProvider'; import useActiveRoute from '@hooks/useActiveRoute'; -import useEnvironment from '@hooks/useEnvironment'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; @@ -466,6 +466,7 @@ function MoneyRequestView({ canEdit, isCustomUnitOutOfPolicy, MoneyRequestView, + companyCardPageURL, ], ); From 4ffbe523e81c492d04a4b004a86aaeaebb0a3ba4 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Fri, 7 Nov 2025 13:05:55 +0500 Subject: [PATCH 08/10] Update src/components/ReportActionItem/MoneyRequestView.tsx Co-authored-by: DylanDylann <141406735+DylanDylann@users.noreply.github.com> --- src/components/ReportActionItem/MoneyRequestView.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index f76ab5e17310..7d8a26048c42 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -465,7 +465,6 @@ function MoneyRequestView({ canEditMerchant, canEdit, isCustomUnitOutOfPolicy, - MoneyRequestView, companyCardPageURL, ], ); From 079826e91c28348a4b5ad7673880704ee18408cc Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Sat, 8 Nov 2025 09:59:46 +0500 Subject: [PATCH 09/10] add ViolationsRterParams --- src/languages/es.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/languages/es.ts b/src/languages/es.ts index b61016aab0c0..1444f7843b37 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1,6 +1,7 @@ import {CONST as COMMON_CONST} from 'expensify-common'; import CONST from '@src/CONST'; import type en from './en'; +import {ViolationsRterParams} from './params'; import type {TranslationDeepObject} from './types'; /* eslint-disable max-len */ From 451fadf75b65608a675e0134ae2598dad835e1f1 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Sat, 8 Nov 2025 10:23:54 +0500 Subject: [PATCH 10/10] fix lint --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 1444f7843b37..c856ce389a0b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1,7 +1,7 @@ import {CONST as COMMON_CONST} from 'expensify-common'; import CONST from '@src/CONST'; import type en from './en'; -import {ViolationsRterParams} from './params'; +import type {ViolationsRterParams} from './params'; import type {TranslationDeepObject} from './types'; /* eslint-disable max-len */