From f9bf9247fe9184ab7944aa88be6f190d218b0d4d Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 7 Aug 2026 17:33:38 +0700 Subject: [PATCH 1/3] Add Auto-categorize new expenses workspace setting to let strict admins disable auto-categorization --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + ...etPolicyAutoCategorizeNewExpensesParams.ts | 6 ++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/actions/Policy/Category.ts | 57 +++++++++++++++++++ ...DynamicWorkspaceCategoriesSettingsPage.tsx | 20 ++++++- src/types/onyx/Policy.ts | 3 + 8 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 src/libs/API/parameters/SetPolicyAutoCategorizeNewExpensesParams.ts diff --git a/src/languages/en.ts b/src/languages/en.ts index 17dc9dd4d1c4..b4b2af98b9bc 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -6086,6 +6086,7 @@ const translations = { deleteFailureMessage: 'An error occurred while deleting the category, please try again', categoryName: 'Category name', requiresCategory: 'Members must categorize all expenses', + autoCategorizeNewExpenses: 'Auto-categorize new expenses', needCategoryForExportToIntegration: (connectionName: string) => `All expenses must be categorized in order to export to ${connectionName}.`, subtitle: 'Get a better overview of where money is being spent. Use our default categories or add your own.', emptyCategories: { diff --git a/src/languages/es.ts b/src/languages/es.ts index 98a2b8083dbf..dbf9abee0df0 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5874,6 +5874,7 @@ ${amount} para ${merchant} - ${date}`, deleteFailureMessage: 'Se ha producido un error al intentar eliminar la categoría. Por favor, inténtalo más tarde.', categoryName: 'Nombre de la categoría', requiresCategory: 'Los miembros deben clasificar todos los gastos', + autoCategorizeNewExpenses: 'Categorizar automáticamente los gastos nuevos', needCategoryForExportToIntegration: (connectionName) => `Todos los gastos deben estar categorizados para poder exportar a ${connectionName}.`, subtitle: 'Obtén una visión general de dónde te gastas el dinero. Utiliza las categorías predeterminadas o añade las tuyas propias.', emptyCategories: { diff --git a/src/libs/API/parameters/SetPolicyAutoCategorizeNewExpensesParams.ts b/src/libs/API/parameters/SetPolicyAutoCategorizeNewExpensesParams.ts new file mode 100644 index 000000000000..058a0b6d6f13 --- /dev/null +++ b/src/libs/API/parameters/SetPolicyAutoCategorizeNewExpensesParams.ts @@ -0,0 +1,6 @@ +type SetPolicyAutoCategorizeNewExpensesParams = { + policyID: string; + enabled: boolean; +}; + +export default SetPolicyAutoCategorizeNewExpensesParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 320241c6baa4..5d0cd0741d46 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -248,6 +248,7 @@ export type {default as SetWorkspaceCategoriesEnabledParams} from './SetWorkspac export type {default as CreateWorkspaceCategoriesParams} from './CreateWorkspaceCategoriesParams'; export type {default as RenameWorkspaceCategoriesParams} from './RenameWorkspaceCategoriesParams'; export type {default as SetWorkspaceRequiresCategoryParams} from './SetWorkspaceRequiresCategoryParams'; +export type {default as SetPolicyAutoCategorizeNewExpensesParams} from './SetPolicyAutoCategorizeNewExpensesParams'; export type {default as DeleteWorkspaceCategoriesParams} from './DeleteWorkspaceCategoriesParams'; export type {default as UpdatePolicyCategoryPayrollCodeParams} from './UpdatePolicyCategoryPayrollCodeParams'; export type {default as UpdatePolicyCategoryGLCodeParams} from './UpdatePolicyCategoryGLCodeParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 8d93dd0d822c..92c471014aef 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -189,6 +189,7 @@ const WRITE_COMMANDS = { CREATE_POLICY_TAG: 'CreatePolicyTag', RENAME_POLICY_TAG: 'RenamePolicyTag', SET_WORKSPACE_REQUIRES_CATEGORY: 'SetWorkspaceRequiresCategory', + SET_POLICY_AUTO_CATEGORIZE_NEW_EXPENSES: 'SetPolicyAutoCategorizeNewExpenses', UPDATE_POLICY_CATEGORY_PAYROLL_CODE: 'UpdatePolicyCategoryPayrollCode', UPDATE_POLICY_CATEGORY_GL_CODE: 'UpdatePolicyCategoryGLCode', DELETE_WORKSPACE_CATEGORIES: 'DeleteWorkspaceCategories', @@ -830,6 +831,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.EXPORT_RECEIPTS_TO_ZIP]: Parameters.ExportReceiptsToZipParams; [WRITE_COMMANDS.RENAME_WORKSPACE_CATEGORY]: Parameters.RenameWorkspaceCategoriesParams; [WRITE_COMMANDS.SET_WORKSPACE_REQUIRES_CATEGORY]: Parameters.SetWorkspaceRequiresCategoryParams; + [WRITE_COMMANDS.SET_POLICY_AUTO_CATEGORIZE_NEW_EXPENSES]: Parameters.SetPolicyAutoCategorizeNewExpensesParams; [WRITE_COMMANDS.DELETE_WORKSPACE_CATEGORIES]: Parameters.DeleteWorkspaceCategoriesParams; [WRITE_COMMANDS.UPDATE_POLICY_CATEGORY_PAYROLL_CODE]: Parameters.UpdatePolicyCategoryPayrollCodeParams; [WRITE_COMMANDS.UPDATE_POLICY_CATEGORY_GL_CODE]: Parameters.UpdatePolicyCategoryGLCodeParams; diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index 31d9dbe58337..5e85440b8f71 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -1268,6 +1268,62 @@ function setWorkspaceRequiresCategory(policyData: PolicyData, requiresCategory: API.write(WRITE_COMMANDS.SET_WORKSPACE_REQUIRES_CATEGORY, parameters, onyxData); } +function setPolicyAutoCategorizeNewExpenses(policyID: string, enabled: boolean) { + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + autoCategorizeNewExpenses: enabled, + errorFields: { + autoCategorizeNewExpenses: null, + }, + pendingFields: { + autoCategorizeNewExpenses: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + errorFields: { + autoCategorizeNewExpenses: null, + }, + pendingFields: { + autoCategorizeNewExpenses: null, + }, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + autoCategorizeNewExpenses: !enabled, + errorFields: { + autoCategorizeNewExpenses: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.categories.updateFailureMessage'), + }, + pendingFields: { + autoCategorizeNewExpenses: null, + }, + }, + }, + ], + }; + + const parameters = { + policyID, + enabled, + }; + + API.write(WRITE_COMMANDS.SET_POLICY_AUTO_CATEGORIZE_NEW_EXPENSES, parameters, onyxData); +} + function clearCategoryErrors(policyID: string, categoryName: string, policyCategories: PolicyCategories = {}) { const category = policyCategories?.[categoryName]; @@ -1928,4 +1984,5 @@ export { setWorkspaceCategoryDescriptionHint, setWorkspaceCategoryEnabled, setWorkspaceRequiresCategory, + setPolicyAutoCategorizeNewExpenses, }; diff --git a/src/pages/workspace/categories/DynamicWorkspaceCategoriesSettingsPage.tsx b/src/pages/workspace/categories/DynamicWorkspaceCategoriesSettingsPage.tsx index ccbc222fae09..8dc0c0f41fae 100644 --- a/src/pages/workspace/categories/DynamicWorkspaceCategoriesSettingsPage.tsx +++ b/src/pages/workspace/categories/DynamicWorkspaceCategoriesSettingsPage.tsx @@ -23,7 +23,7 @@ import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnec import withPolicyConnections from '@pages/workspace/withPolicyConnections'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; -import {setWorkspaceRequiresCategory} from '@userActions/Policy/Category'; +import {setPolicyAutoCategorizeNewExpenses, setWorkspaceRequiresCategory} from '@userActions/Policy/Category'; import {clearPolicyErrorField} from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; @@ -57,6 +57,13 @@ function DynamicWorkspaceCategoriesSettingsPage({policy, route}: DynamicWorkspac [policyData], ); + const updateAutoCategorizeNewExpenses = useCallback( + (value: boolean) => { + setPolicyAutoCategorizeNewExpenses(policyID, value); + }, + [policyID], + ); + const data = useMemo(() => { if (!policyData.policy?.mccGroup) { return []; @@ -125,6 +132,17 @@ function DynamicWorkspaceCategoriesSettingsPage({policy, route}: DynamicWorkspac onCloseError={() => clearPolicyErrorField(policy?.id, 'requiresCategory')} shouldPlaceSubtitleBelowSwitch /> + clearPolicyErrorField(policy?.id, 'autoCategorizeNewExpenses')} + /> {!!policyData.policy && (data?.length ?? 0) > 0 && ( diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 8283edadbae9..3d75fe4d4711 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -2638,6 +2638,9 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< /** Whether new transactions need to be categorized */ requiresCategory?: boolean; + /** Whether new uncategorized expenses get a category picked for them automatically. Defaults to true when unset. */ + autoCategorizeNewExpenses?: boolean; + /** * Policy Receipt Partners */ From 40bebb1f553372c2afe82345ed8afd581f28b1f7 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 25 Aug 2026 09:21:01 +0700 Subject: [PATCH 2/3] update translation --- src/languages/de.ts | 1 + src/languages/el.ts | 1 + src/languages/fr.ts | 1 + src/languages/it.ts | 1 + src/languages/ja.ts | 1 + src/languages/nl.ts | 1 + src/languages/pl.ts | 1 + src/languages/pt-BR.ts | 1 + src/languages/zh-hans.ts | 1 + 9 files changed, 9 insertions(+) diff --git a/src/languages/de.ts b/src/languages/de.ts index 350c76a708d6..ff78dcc5ad55 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -6173,6 +6173,7 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU deleteFailureMessage: 'Beim Löschen der Kategorie ist ein Fehler aufgetreten, bitte versuche es erneut.', categoryName: 'Kategoriename', requiresCategory: 'Mitglieder müssen alle Ausgaben kategorisieren', + autoCategorizeNewExpenses: 'Neue Ausgaben automatisch kategorisieren', showCategoryGLCodes: 'Sachkonten beim Kategorisieren von Ausgaben anzeigen', needCategoryForExportToIntegration: (connectionName: string) => `Alle Ausgaben müssen kategorisiert werden, um nach ${connectionName} exportiert zu werden.`, subtitle: 'Verschaffe dir einen besseren Überblick darüber, wofür Geld ausgegeben wird. Verwende unsere Standardkategorien oder füge eigene hinzu.', diff --git a/src/languages/el.ts b/src/languages/el.ts index 0bd126ad2004..a89b04e5e68f 100644 --- a/src/languages/el.ts +++ b/src/languages/el.ts @@ -6289,6 +6289,7 @@ _Για πιο αναλυτικές οδηγίες, [επισκεφθείτε τ deleteFailureMessage: 'Παρουσιάστηκε σφάλμα κατά τη διαγραφή της κατηγορίας, δοκιμάστε ξανά', categoryName: 'Όνομα κατηγορίας', requiresCategory: 'Τα μέλη πρέπει να κατηγοριοποιούν όλες τις δαπάνες', + autoCategorizeNewExpenses: 'Αυτόματη κατηγοριοποίηση νέων δαπανών', showCategoryGLCodes: 'Εμφάνιση κωδικών GL κατά την κατηγοριοποίηση δαπανών', needCategoryForExportToIntegration: (connectionName: string) => `Όλες οι δαπάνες πρέπει να κατηγοριοποιηθούν για να γίνει η εξαγωγή στο ${connectionName}.`, subtitle: 'Αποκτήστε μια καλύτερη εικόνα για το πού ξοδεύονται τα χρήματα. Χρησιμοποιήστε τις προεπιλεγμένες κατηγορίες μας ή προσθέστε τις δικές σας.', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 360e952405a3..3b502e419663 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -6181,6 +6181,7 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST. deleteFailureMessage: "Une erreur s'est produite lors de la suppression de la catégorie, veuillez réessayer", categoryName: 'Nom de la catégorie', requiresCategory: 'Les membres doivent catégoriser toutes les dépenses', + autoCategorizeNewExpenses: 'Catégoriser automatiquement les nouvelles dépenses', showCategoryGLCodes: 'Afficher les codes de grand livre lors de la catégorisation des dépenses', needCategoryForExportToIntegration: (connectionName: string) => `Toutes les dépenses doivent être catégorisées afin de pouvoir être exportées vers ${connectionName}.`, subtitle: 'Obtenez une meilleure vue d’ensemble de l’endroit où l’argent est dépensé. Utilisez nos catégories par défaut ou ajoutez les vôtres.', diff --git a/src/languages/it.ts b/src/languages/it.ts index a42b8129f72d..b12387ce4968 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -6136,6 +6136,7 @@ _Per istruzioni più dettagliate, [visita il nostro sito di assistenza](${CONST. deleteFailureMessage: 'Si è verificato un errore durante l’eliminazione della categoria, riprova per favore', categoryName: 'Nome categoria', requiresCategory: 'I membri devono categorizzare tutte le spese', + autoCategorizeNewExpenses: 'Categorizza automaticamente le nuove spese', showCategoryGLCodes: 'Mostra i codici CO.GE. quando classifichi le spese', needCategoryForExportToIntegration: (connectionName: string) => `Tutte le spese devono essere categorizzate per poterle esportare su ${connectionName}.`, subtitle: 'Ottieni una panoramica migliore di dove viene speso il denaro. Usa le nostre categorie predefinite oppure aggiungi le tue.', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 9e2be4fbdedc..7530269df7ce 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -6062,6 +6062,7 @@ _詳しい手順については、[ヘルプサイトをご覧ください](${CO deleteFailureMessage: 'カテゴリの削除中にエラーが発生しました。もう一度お試しください', categoryName: 'カテゴリ名', requiresCategory: 'メンバーはすべての経費を分類する必要があります', + autoCategorizeNewExpenses: '新しい経費を自動分類する', showCategoryGLCodes: '経費を分類するときに GL コードを表示する', needCategoryForExportToIntegration: (connectionName: string) => `${connectionName} にエクスポートするには、すべての経費にカテゴリを指定する必要があります。`, subtitle: 'お金がどこで使われているかを、より分かりやすく把握しましょう。デフォルトのカテゴリを使うか、自分用のカテゴリを追加できます。', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 1cb288f87727..557654022a55 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -6128,6 +6128,7 @@ _Voor meer gedetailleerde instructies, [bezoek onze help-site](${CONST.NETSUITE_ deleteFailureMessage: 'Er is een fout opgetreden bij het verwijderen van de categorie, probeer het opnieuw', categoryName: 'Categorienaam', requiresCategory: 'Leden moeten alle uitgaven categoriseren', + autoCategorizeNewExpenses: 'Nieuwe uitgaven automatisch categoriseren', showCategoryGLCodes: 'Toon GL-codes bij het categoriseren van uitgaven', needCategoryForExportToIntegration: (connectionName: string) => `Alle onkosten moeten worden gecategoriseerd om te kunnen exporteren naar ${connectionName}.`, subtitle: 'Krijg beter inzicht in waar geld wordt uitgegeven. Gebruik onze standaardcategorieën of voeg je eigen categorieën toe.', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 7aa534a9a1a6..a0ab1fccf4c5 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -6149,6 +6149,7 @@ _Aby uzyskać bardziej szczegółowe instrukcje, [odwiedź naszą stronę pomocy deleteFailureMessage: 'Wystąpił błąd podczas usuwania kategorii, spróbuj ponownie', categoryName: 'Nazwa kategorii', requiresCategory: 'Członkowie muszą kategoryzować wszystkie wydatki', + autoCategorizeNewExpenses: 'Automatycznie kategoryzuj nowe wydatki', showCategoryGLCodes: 'Pokaż kody GL podczas kategoryzowania wydatków', needCategoryForExportToIntegration: (connectionName: string) => `Wszystkie wydatki muszą zostać skategoryzowane, aby można je było wyeksportować do ${connectionName}.`, subtitle: 'Uzyskaj lepszy wgląd w to, gdzie wydawane są pieniądze. Użyj naszych domyślnych kategorii lub dodaj własne.', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index f36781529816..ebc737017fa6 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -6126,6 +6126,7 @@ _Para instruções mais detalhadas, [visite nossa central de ajuda](${CONST.NETS deleteFailureMessage: 'Ocorreu um erro ao excluir a categoria, tente novamente', categoryName: 'Nome da categoria', requiresCategory: 'Os membros devem categorizar todas as despesas', + autoCategorizeNewExpenses: 'Categorizar automaticamente novas despesas', showCategoryGLCodes: 'Mostrar códigos GL ao categorizar despesas', needCategoryForExportToIntegration: (connectionName: string) => `Todas as despesas devem ser categorizadas para serem exportadas para ${connectionName}.`, subtitle: 'Tenha uma visão melhor de onde o dinheiro está sendo gasto. Use nossas categorias padrão ou adicione as suas.', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 702e34aa31f9..17956873d512 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -5925,6 +5925,7 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM deleteFailureMessage: '删除类别时出错,请重试', categoryName: '类别名称', requiresCategory: '成员必须为所有报销分类', + autoCategorizeNewExpenses: '自动为新报销分类', showCategoryGLCodes: '在分类报销时显示总账科目代码', needCategoryForExportToIntegration: (connectionName: string) => `要导出到 ${connectionName},所有报销都必须先进行分类。`, subtitle: '更好地了解资金的支出去向。使用我们的默认类别或添加你自己的类别。', From eabffbd5ed3a7b628f207850f1438b3cce2087d7 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 27 Aug 2026 15:34:50 +0700 Subject: [PATCH 3/3] do not show Analyzing... when autoCategorizeNewExpenses is disabled --- .../ReportActionItem/MoneyRequestView.tsx | 2 +- src/libs/TransactionUtils/index.ts | 11 ++++- tests/unit/TransactionUtilsTest.ts | 40 +++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index a171c743379b..adfdd5af30a4 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -1154,7 +1154,7 @@ function MoneyRequestView({ const reportNameToDisplay = isFromMergeTransaction ? (updatedTransaction?.reportName ?? translate('common.none')) : getReportName(parentReport, parentReportDerivedName); const shouldShowReport = !!parentReportID || (isFromMergeTransaction && !!reportNameToDisplay); const reportCopyValue = !canEditReport && reportNameToDisplay !== translate('common.none') ? reportNameToDisplay : undefined; - const shouldShowCategoryAnalyzing = isCategoryBeingAnalyzed(updatedTransaction ?? transaction); + const shouldShowCategoryAnalyzing = isCategoryBeingAnalyzed(updatedTransaction ?? transaction, policy); // In this case we want to use this value. The shouldUseNarrowLayout will always be true as this case is handled when we display ReportScreen in RHP. // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 204babcaddbd..47f42c1f72e3 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1795,12 +1795,19 @@ function isReceiptBeingScanned(transaction: OnyxInputOrEntry): bool /** * Check if category is being analyzed (manual request creation or auto-categorization grace period) + * + * @param transaction - The transaction whose category may still be auto-categorized + * @param policy - The workspace policy; auto-categorize defaults to on when the attribute is unset */ -function isCategoryBeingAnalyzed(transaction: OnyxEntry): boolean { +function isCategoryBeingAnalyzed(transaction: OnyxEntry, policy?: OnyxEntry): boolean { if (!transaction) { return false; } + if (policy?.autoCategorizeNewExpenses === false) { + return false; + } + if (isExpenseUnreported(transaction)) { return false; } @@ -2095,7 +2102,7 @@ function shouldShowViolation( return isAttendeeTrackingEnabledForPolicy(policy); } - if (violationName === CONST.VIOLATIONS.MISSING_CATEGORY && isCategoryBeingAnalyzed(transaction)) { + if (violationName === CONST.VIOLATIONS.MISSING_CATEGORY && isCategoryBeingAnalyzed(transaction, policy)) { return false; } diff --git a/tests/unit/TransactionUtilsTest.ts b/tests/unit/TransactionUtilsTest.ts index ecfa9cf3aaa4..8041c8d1cb6e 100644 --- a/tests/unit/TransactionUtilsTest.ts +++ b/tests/unit/TransactionUtilsTest.ts @@ -2342,6 +2342,30 @@ describe('TransactionUtils', () => { expect(TransactionUtils.isCategoryBeingAnalyzed(transaction)).toBe(true); }); + it('should return false when auto-categorize new expenses is disabled on the policy', () => { + const transaction = generateTransaction({ + category: '', + merchant: 'Some Merchant', + amount: 100, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }); + const policy = {...createRandomPolicy(0), autoCategorizeNewExpenses: false}; + + expect(TransactionUtils.isCategoryBeingAnalyzed(transaction, policy)).toBe(false); + }); + + it('should return true when auto-categorize new expenses is enabled on the policy', () => { + const transaction = generateTransaction({ + category: '', + merchant: 'Some Merchant', + amount: 100, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }); + const policy = {...createRandomPolicy(0), autoCategorizeNewExpenses: true}; + + expect(TransactionUtils.isCategoryBeingAnalyzed(transaction, policy)).toBe(true); + }); + it('should return true when within auto-categorization grace period', () => { // Set pendingAutoCategorizationTime to 30 seconds ago (within 1 minute grace period) const thirtySecondsAgo = new Date(Date.now() - 30 * 1000); @@ -2359,6 +2383,22 @@ describe('TransactionUtils', () => { expect(TransactionUtils.isCategoryBeingAnalyzed(transaction)).toBe(true); }); + it('should return false during the grace period when auto-categorize new expenses is disabled', () => { + const thirtySecondsAgo = new Date(Date.now() - 30 * 1000); + const pendingAutoCategorizationTime = thirtySecondsAgo.toISOString().replace('T', ' ').replace('Z', ''); + const transaction = generateTransaction({ + category: '', + merchant: 'Some Merchant', + amount: 100, + comment: { + pendingAutoCategorizationTime, + }, + }); + const policy = {...createRandomPolicy(0), autoCategorizeNewExpenses: false}; + + expect(TransactionUtils.isCategoryBeingAnalyzed(transaction, policy)).toBe(false); + }); + it('should return false when auto-categorization grace period has passed', () => { // Set pendingAutoCategorizationTime to 2 minutes ago (outside 1 minute grace period) const twoMinutesAgo = new Date(Date.now() - 2 * 60 * 1000);