diff --git a/src/languages/de.ts b/src/languages/de.ts index f3b0cb43e3bf7..a916af2c1e661 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -1339,7 +1339,7 @@ const translations: TranslationDeepObject = { updatedTheDistanceMerchant: ({translatedChangedField, newMerchant, oldMerchant, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceMerchantParams) => `änderte das ${translatedChangedField} zu ${newMerchant} (zuvor ${oldMerchant}), wodurch der Betrag auf ${newAmountToDisplay} aktualisiert wurde (zuvor ${oldAmountToDisplay})`, basedOnAI: 'basierend auf früheren Aktivitäten', - basedOnMCC: 'basierend auf Arbeitsbereichsregel', + basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `gemäß den Regeln des Arbeitsbereichs` : 'gemäß der Arbeitsbereichsregel'), threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `für ${comment}` : 'Ausgabe'}`, invoiceReportName: ({linkedReportID}: OriginalMessage) => `Rechnungsbericht Nr. ${linkedReportID}`, threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} gesendet${comment ? `für ${comment}` : ''}`, diff --git a/src/languages/en.ts b/src/languages/en.ts index 2a35a7fa20742..3940dd12abe5e 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1321,7 +1321,7 @@ const translations = { updatedTheDistanceMerchant: ({translatedChangedField, newMerchant, oldMerchant, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceMerchantParams) => `changed the ${translatedChangedField} to ${newMerchant} (previously ${oldMerchant}), which updated the amount to ${newAmountToDisplay} (previously ${oldAmountToDisplay})`, basedOnAI: 'based on past activity', - basedOnMCC: 'based on workspace rule', + basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `based on workspace rules` : 'based on workspace rule'), threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `for ${comment}` : 'expense'}`, invoiceReportName: ({linkedReportID}: OriginalMessage) => `Invoice Report #${linkedReportID}`, threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} sent${comment ? ` for ${comment}` : ''}`, diff --git a/src/languages/es.ts b/src/languages/es.ts index e28676dc10dbe..e3b1b7f159b50 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -982,7 +982,7 @@ const translations: TranslationDeepObject = { updatedTheDistanceMerchant: ({translatedChangedField, newMerchant, oldMerchant, newAmountToDisplay, oldAmountToDisplay}) => `cambió la ${translatedChangedField} a ${newMerchant} (previamente ${oldMerchant}), lo que cambió el importe a ${newAmountToDisplay} (previamente ${oldAmountToDisplay})`, basedOnAI: 'basado en actividad pasada', - basedOnMCC: 'basado en regla del espacio de trabajo', + basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `basado en reglas del espacio de trabajo` : 'basado en regla del espacio de trabajo'), threadExpenseReportName: ({formattedAmount, comment}) => `${comment ? `${formattedAmount} para ${comment}` : `Gasto de ${formattedAmount}`}`, invoiceReportName: ({linkedReportID}) => `Informe de facturación #${linkedReportID}`, threadPaySomeoneReportName: ({formattedAmount, comment}) => `${formattedAmount} enviado${comment ? ` para ${comment}` : ''}`, diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 490e628737e37..8f5e60da72bfd 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -1343,7 +1343,7 @@ const translations: TranslationDeepObject = { updatedTheDistanceMerchant: ({translatedChangedField, newMerchant, oldMerchant, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceMerchantParams) => `a changé le ${translatedChangedField} en ${newMerchant} (précédemment ${oldMerchant}), ce qui a mis à jour le montant à ${newAmountToDisplay} (précédemment ${oldAmountToDisplay})`, basedOnAI: "basé sur l'activité passée", - basedOnMCC: "basé sur la règle de l'espace de travail", + basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `selon les règles de l'espace de travail` : 'selon la règle de l’espace de travail'), threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `pour ${comment}` : 'dépense'}`, invoiceReportName: ({linkedReportID}: OriginalMessage) => `Rapport de Facture n°${linkedReportID}`, threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} envoyé${comment ? `pour ${comment}` : ''}`, diff --git a/src/languages/it.ts b/src/languages/it.ts index 22f55093723e7..396f181f76a94 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -1337,7 +1337,8 @@ const translations: TranslationDeepObject = { updatedTheDistanceMerchant: ({translatedChangedField, newMerchant, oldMerchant, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceMerchantParams) => `ha cambiato il ${translatedChangedField} in ${newMerchant} (precedentemente ${oldMerchant}), il che ha aggiornato l'importo a ${newAmountToDisplay} (precedentemente ${oldAmountToDisplay})`, basedOnAI: 'basato su attività passate', - basedOnMCC: 'basato su regola dello spazio di lavoro', + basedOnMCC: ({rulesLink}: {rulesLink: string}) => + rulesLink ? `in base alle regole dello spazio di lavoro` : 'in base alla regola dello spazio di lavoro', threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `per ${comment}` : 'spesa'}`, invoiceReportName: ({linkedReportID}: OriginalMessage) => `Rapporto Fattura n. ${linkedReportID}`, threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} inviato${comment ? `per ${comment}` : ''}`, diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 5489423e508d9..f2e7cb632c088 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -1339,7 +1339,7 @@ const translations: TranslationDeepObject = { updatedTheDistanceMerchant: ({translatedChangedField, newMerchant, oldMerchant, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceMerchantParams) => `${translatedChangedField}を${newMerchant}に変更しました(以前は${oldMerchant})、これにより金額が${newAmountToDisplay}に更新されました(以前は${oldAmountToDisplay})。`, basedOnAI: '過去のアクティビティに基づく', - basedOnMCC: 'ワークスペースルールに基づく', + basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `ワークスペースのルールに基づいて` : 'ワークスペースのルールに基づく'), threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `${comment}用` : '経費'}`, invoiceReportName: ({linkedReportID}: OriginalMessage) => `請求書レポート #${linkedReportID}`, threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} 送信済み${comment ? `${comment} のために` : ''}`, diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 35a1c6aa15b28..744f0cf10a980 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -1337,7 +1337,7 @@ const translations: TranslationDeepObject = { updatedTheDistanceMerchant: ({translatedChangedField, newMerchant, oldMerchant, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceMerchantParams) => `veranderde de ${translatedChangedField} naar ${newMerchant} (voorheen ${oldMerchant}), wat het bedrag bijwerkte naar ${newAmountToDisplay} (voorheen ${oldAmountToDisplay})`, basedOnAI: 'op basis van eerdere activiteit', - basedOnMCC: 'op basis van werkruimteregel', + basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `op basis van werkruimteregels` : 'gebaseerd op werkruimteregel'), threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `voor ${comment}` : 'uitgave'}`, invoiceReportName: ({linkedReportID}: OriginalMessage) => `Factuurrapport #${linkedReportID}`, threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} verzonden${comment ? `voor ${comment}` : ''}`, diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 6ee7de5d3db64..00ed80bd152ab 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -1336,7 +1336,7 @@ const translations: TranslationDeepObject = { updatedTheDistanceMerchant: ({translatedChangedField, newMerchant, oldMerchant, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceMerchantParams) => `zmienił ${translatedChangedField} na ${newMerchant} (wcześniej ${oldMerchant}), co zaktualizowało kwotę na ${newAmountToDisplay} (wcześniej ${oldAmountToDisplay})`, basedOnAI: 'na podstawie wcześniejszej aktywności', - basedOnMCC: 'na podstawie reguły przestrzeni roboczej', + basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `na podstawie zasad obszaru roboczego` : 'na podstawie reguły obszaru roboczego'), threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `dla ${comment}` : 'wydatek'}`, invoiceReportName: ({linkedReportID}: OriginalMessage) => `Raport faktury nr ${linkedReportID}`, threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} wysłano${comment ? `dla ${comment}` : ''}`, diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 5ebbabf2e8d1d..0c61710f54383 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -1335,7 +1335,7 @@ const translations: TranslationDeepObject = { updatedTheDistanceMerchant: ({translatedChangedField, newMerchant, oldMerchant, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceMerchantParams) => `alterou o ${translatedChangedField} para ${newMerchant} (anteriormente ${oldMerchant}), o que atualizou o valor para ${newAmountToDisplay} (anteriormente ${oldAmountToDisplay})`, basedOnAI: 'com base em atividades passadas', - basedOnMCC: 'com base na regra do espaço de trabalho', + basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `com base nas regras do espaço de trabalho` : 'com base na regra do espaço de trabalho'), threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `para ${comment}` : 'despesa'}`, invoiceReportName: ({linkedReportID}: OriginalMessage) => `Relatório de Fatura nº ${linkedReportID}`, threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} enviado${comment ? `para ${comment}` : ''}`, diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 451b6b444bb99..10a248479afc2 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -1317,7 +1317,7 @@ const translations: TranslationDeepObject = { updatedTheDistanceMerchant: ({translatedChangedField, newMerchant, oldMerchant, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceMerchantParams) => `将${translatedChangedField}更改为${newMerchant}(之前为${oldMerchant}),这更新了金额为${newAmountToDisplay}(之前为${oldAmountToDisplay})`, basedOnAI: '基于过去的活动', - basedOnMCC: '基于工作空间规则', + basedOnMCC: ({rulesLink}: {rulesLink: string}) => (rulesLink ? `基于 工作区规则` : '基于工作区规则'), threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `为${comment}` : '费用'}`, invoiceReportName: ({linkedReportID}: OriginalMessage) => `发票报告 #${linkedReportID}`, threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} 已发送${comment ? `对于${comment}` : ''}`, diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index 22829175d4ea2..7d837d6f0ae68 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -4,22 +4,24 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PolicyTagLists, Report, ReportAction} from '@src/types/onyx'; +import ROUTES from '@src/ROUTES'; +import type {Policy, PolicyTagLists, Report, ReportAction} from '@src/types/onyx'; import {getDecodedCategoryName} from './CategoryUtils'; import {convertToDisplayString} from './CurrencyUtils'; import DateUtils from './DateUtils'; +import {getEnvironmentURL} from './Environment/Environment'; // eslint-disable-next-line @typescript-eslint/no-deprecated import {translateLocal} from './Localize'; import Log from './Log'; import Parser from './Parser'; -import {getCleanedTagName, getSortedTagKeys} from './PolicyUtils'; +import {getCleanedTagName, getPolicy, getSortedTagKeys, isPolicyAdmin} from './PolicyUtils'; import {getOriginalMessage, isModifiedExpenseAction} from './ReportActionsUtils'; // eslint-disable-next-line import/no-cycle import {buildReportNameFromParticipantNames, getPolicyExpenseChatName, getPolicyName, getReportName, getRootParentReport, isPolicyExpenseChat, isSelfDM} from './ReportUtils'; import {getFormattedAttendees, getTagArrayFromName} from './TransactionUtils'; let allPolicyTags: OnyxCollection = {}; -Onyx.connect({ +Onyx.connectWithoutView({ key: ONYXKEYS.COLLECTION.POLICY_TAGS, waitForCollectionCallback: true, callback: (value) => { @@ -31,6 +33,21 @@ Onyx.connect({ }, }); +let environmentURL: string; +getEnvironmentURL().then((url: string) => (environmentURL = url)); + +let currentUserLogin = ''; +Onyx.connectWithoutView({ + key: ONYXKEYS.SESSION, + callback: (value) => { + // When signed out, value is undefined + if (!value) { + return; + } + currentUserLogin = value?.email ?? ''; + }, +}); + /** * Builds the partial message fragment for a modified field on the expense. */ @@ -283,7 +300,7 @@ function getForReportAction({ const hasModifiedCategory = isReportActionOriginalMessageAnObject && 'oldCategory' in reportActionOriginalMessage && 'category' in reportActionOriginalMessage; if (hasModifiedCategory) { // eslint-disable-next-line @typescript-eslint/no-deprecated - let categoryLabel = translateLocal('common.category'); + let categoryLabel = translateLocal('common.category').toLowerCase(); // Add attribution suffix based on source if (reportActionOriginalMessage?.source === CONST.CATEGORY_SOURCE.AI) { @@ -291,7 +308,18 @@ function getForReportAction({ categoryLabel += ` ${translateLocal('iou.basedOnAI')}`; } else if (reportActionOriginalMessage?.source === CONST.CATEGORY_SOURCE.MCC) { // eslint-disable-next-line @typescript-eslint/no-deprecated - categoryLabel += ` ${translateLocal('iou.basedOnMCC')}`; + const policy = getPolicy(policyID); + const isAdmin = isPolicyAdmin(policy, currentUserLogin); + + // For admins, create a hyperlink to the workspace rules page + if (isAdmin && policy?.id) { + const rulesLink = `${environmentURL}/${ROUTES.WORKSPACE_RULES.getRoute(policy.id)}`; + // eslint-disable-next-line @typescript-eslint/no-deprecated + categoryLabel += ` ${translateLocal('iou.basedOnMCC', {rulesLink})}`; + } else { + // eslint-disable-next-line @typescript-eslint/no-deprecated + categoryLabel += ` ${translateLocal('iou.basedOnMCC', {rulesLink: ''})}`; + } } buildMessageFragmentForValue( @@ -302,6 +330,8 @@ function getForReportAction({ setFragments, removalFragments, changeFragments, + // Don't convert to lowercase when we have source attribution (to preserve any HTML links) + false, ); } @@ -436,11 +466,13 @@ function getForReportAction({ */ function getForReportActionTemp({ reportAction, + policy, movedFromReport, movedToReport, policyTags, }: { reportAction: OnyxEntry; + policy?: OnyxEntry; movedFromReport?: OnyxEntry; movedToReport?: OnyxEntry; policyTags: OnyxEntry; @@ -531,15 +563,24 @@ function getForReportActionTemp({ const hasModifiedCategory = isReportActionOriginalMessageAnObject && 'oldCategory' in reportActionOriginalMessage && 'category' in reportActionOriginalMessage; if (hasModifiedCategory) { // eslint-disable-next-line @typescript-eslint/no-deprecated - let categoryLabel = translateLocal('common.category'); + let categoryLabel = translateLocal('common.category').toLowerCase(); // Add attribution suffix based on source if (reportActionOriginalMessage?.source === CONST.CATEGORY_SOURCE.AI) { // eslint-disable-next-line @typescript-eslint/no-deprecated categoryLabel += ` ${translateLocal('iou.basedOnAI')}`; } else if (reportActionOriginalMessage?.source === CONST.CATEGORY_SOURCE.MCC) { - // eslint-disable-next-line @typescript-eslint/no-deprecated - categoryLabel += ` ${translateLocal('iou.basedOnMCC')}`; + const isAdmin = isPolicyAdmin(policy, currentUserLogin); + + // For admins, create a hyperlink to the workspace rules page + if (isAdmin && policy?.id) { + const rulesLink = `${environmentURL}/${ROUTES.WORKSPACE_RULES.getRoute(policy.id)}`; + // eslint-disable-next-line @typescript-eslint/no-deprecated + categoryLabel += ` ${translateLocal('iou.basedOnMCC', {rulesLink})}`; + } else { + // eslint-disable-next-line @typescript-eslint/no-deprecated + categoryLabel += ` ${translateLocal('iou.basedOnMCC', {rulesLink: ''})}`; + } } buildMessageFragmentForValue( @@ -550,6 +591,8 @@ function getForReportActionTemp({ setFragments, removalFragments, changeFragments, + // Don't convert to lowercase when we have source attribution (to preserve any HTML links) + false, ); } diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx index 7f41640d56d6d..0000f4330dddd 100755 --- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx @@ -161,6 +161,7 @@ function BaseReportActionContextMenu({ const [originalReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${originalReportID}`, {canBeMissing: true}); const isOriginalReportArchived = useReportIsArchived(originalReportID); const policyID = report?.policyID; + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true}); const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true}); const [movedFromReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getMovedReportID(reportAction, CONST.REPORT.MOVE_TYPE.FROM)}`, {canBeMissing: true}); @@ -374,6 +375,7 @@ function BaseReportActionContextMenu({ movedFromReport, movedToReport, getLocalDateFromDatetime, + policy, policyTags, translate, }; diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index 58ad26025c3b6..66a93189fba30 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -217,6 +217,7 @@ type ContextMenuActionPayload = { movedFromReport?: OnyxEntry; movedToReport?: OnyxEntry; getLocalDateFromDatetime: LocaleContextProps['getLocalDateFromDatetime']; + policy?: OnyxEntry; policyTags: OnyxEntry; translate: LocalizedTranslate; }; @@ -541,6 +542,7 @@ const ContextMenuActions: ContextMenuAction[] = [ movedToReport, childReport, getLocalDateFromDatetime, + policy, policyTags, translate, }, @@ -563,6 +565,7 @@ const ContextMenuActions: ContextMenuAction[] = [ } else if (isModifiedExpenseAction(reportAction)) { const modifyExpenseMessage = getForReportActionTemp({ reportAction, + policy, movedFromReport, movedToReport, policyTags, diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index fdae38a4c7081..ca7cea363d860 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -1169,7 +1169,11 @@ function PureReportActionItem({ } else if (isReimbursementDeQueuedOrCanceledAction(action)) { children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE) { - children = ; + children = ( + + ${modifiedExpenseMessage}`} /> + + ); } else if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED) || isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED_AND_CLOSED) || isMarkAsClosedAction(action)) { const wasSubmittedViaHarvesting = !isMarkAsClosedAction(action) ? (getOriginalMessage(action)?.harvesting ?? false) : false; if (wasSubmittedViaHarvesting) { diff --git a/tests/unit/ModifiedExpenseMessageTest.ts b/tests/unit/ModifiedExpenseMessageTest.ts index 5e8551678fb61..d3825740f58e7 100644 --- a/tests/unit/ModifiedExpenseMessageTest.ts +++ b/tests/unit/ModifiedExpenseMessageTest.ts @@ -1,9 +1,12 @@ import {getForReportAction, getMovedFromOrToReportMessage, getMovedReportID} from '@libs/ModifiedExpenseMessage'; // eslint-disable-next-line no-restricted-syntax -- this is required to allow mocking +import * as PolicyUtils from '@libs/PolicyUtils'; +// eslint-disable-next-line no-restricted-syntax -- this is required to allow mocking import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import IntlStore from '@src/languages/IntlStore'; import {translate} from '@src/libs/Localize'; +import type {Policy} from '@src/types/onyx'; import type {OriginalMessageModifiedExpense} from '@src/types/onyx/OriginalMessage'; import createRandomReportAction from '../utils/collections/reportActions'; import {createRandomReport} from '../utils/collections/reports'; @@ -680,13 +683,39 @@ describe('ModifiedExpenseMessage', () => { } as OriginalMessageModifiedExpense, }; - it('returns the correct text message with MCC attribution', () => { + it('returns the correct text message with MCC attribution for non-admin', () => { const expectedResult = `changed the category based on workspace rule to "Travel" (previously "Food")`; const result = getForReportAction({reportAction, policyID: report.policyID}); expect(result).toEqual(expectedResult); }); + + it('returns the correct workspace rules link for admin', () => { + // The shouldConvertToLowercase: !source parameter prevents buildMessageFragmentForValue + // from calling .toLowerCase() on the entire HTML anchor tag, which would corrupt + // the policyID in the href attribute and cause navigation to fail. + + const mockPolicy: Policy = { + id: 'AbC123XyZ789', // Mixed case to verify exact preservation + name: 'Test Policy', + role: CONST.POLICY.ROLE.ADMIN, + type: CONST.POLICY.TYPE.TEAM, + owner: 'test@example.com', + outputCurrency: 'USD', + isPolicyExpenseChatEnabled: true, + }; + + jest.spyOn(PolicyUtils, 'getPolicy').mockReturnValue(mockPolicy); + jest.spyOn(PolicyUtils, 'isPolicyAdmin').mockReturnValue(true); + + const result = getForReportAction({reportAction, policyID: mockPolicy.id}); + + // Verify the policyID in the URL exactly matches the policy.id (case-preserved) + expect(result).toContain(`workspaces/${mockPolicy.id}/rules`); + expect(result).toContain('href='); + expect(result).toContain('workspace rules'); + }); }); describe('when the category is set with AI attribution', () => {