From 383e409a12853a166fd3080fa42dc71d411ea150 Mon Sep 17 00:00:00 2001 From: KJ21-ENG Date: Mon, 4 May 2026 19:54:37 +0530 Subject: [PATCH 1/2] Expose Accounting suggested searches to Auditors Update getSuggestedSearchesVisibility in src/libs/SearchUIUtils.ts to treat the workspace Auditor role the same as Admin for the four Accounting-section eligibility flags (Unapproved cash, Unapproved card, Expensify Card, Reimbursements). Auditors already have read access to all reports on the workspace, so hiding the Accounting suggested-search shortcuts from them was inconsistent. The change mirrors the existing isAuditor handling on the Top Spenders flag a few lines below. Fixes https://github.com/Expensify/App/issues/89173 --- src/libs/SearchUIUtils.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index bb003aff7e9e..1838d0eb6346 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -1040,6 +1040,7 @@ function getSuggestedSearchesVisibility( const isPaidPolicy = isPaidGroupPolicy(policy); const isPayer = isPolicyPayer(policy, currentUserEmail); const isAdmin = policy.role === CONST.POLICY.ROLE.ADMIN; + const isAuditor = policy.role === CONST.POLICY.ROLE.AUDITOR; const isExporter = policy.exporter === currentUserEmail; const isSubmittedTo = @@ -1060,11 +1061,10 @@ function getSuggestedSearchesVisibility( const isPolicyEligibleForApproveSuggestion = isPaidPolicy && isEligibleForApproveSuggestion(policy.approvalMode, isUserApprover, isSubmittedTo); const isEligibleForExportSuggestion = isExporter && !hasExportError; const isEligibleForStatementsSuggestion = isPaidPolicy && !!policy.areCompanyCardsEnabled && hasCardFeed; - const isEligibleForUnapprovedCashSuggestion = isPaidPolicy && isAdmin && isApprovalEnabled && isPaymentEnabled; - const isEligibleForUnapprovedCardSuggestion = isPaidPolicy && isAdmin && isApprovalEnabled && (hasCardFeed || !!defaultExpensifyCard); - const isEligibleForExpensifyCardSuggestion = isPaidPolicy && isAdmin && isECardEnabled; - const isEligibleForReimbursementsSuggestion = isPaidPolicy && isAdmin && isPaymentEnabled && hasVBBA && hasReimburser; - const isAuditor = policy.role === CONST.POLICY.ROLE.AUDITOR; + const isEligibleForUnapprovedCashSuggestion = isPaidPolicy && (isAdmin || isAuditor) && isApprovalEnabled && isPaymentEnabled; + const isEligibleForUnapprovedCardSuggestion = isPaidPolicy && (isAdmin || isAuditor) && isApprovalEnabled && (hasCardFeed || !!defaultExpensifyCard); + const isEligibleForExpensifyCardSuggestion = isPaidPolicy && (isAdmin || isAuditor) && isECardEnabled; + const isEligibleForReimbursementsSuggestion = isPaidPolicy && (isAdmin || isAuditor) && isPaymentEnabled && hasVBBA && hasReimburser; const memberCount = Object.keys(policy.employeeList ?? {}).length; const isEligibleForTopSpendersSuggestion = isPaidPolicy && (isAdmin || isAuditor || isUserApprover) && memberCount >= 2; const isEligibleForTopCategoriesSuggestion = isPaidPolicy && policy.areCategoriesEnabled === true; From 59b99d5370913d3dddbd3485e1cf6eae4a2e5759 Mon Sep 17 00:00:00 2001 From: KJ21-ENG Date: Mon, 4 May 2026 20:31:02 +0530 Subject: [PATCH 2/2] Add unit tests for Auditor visibility on Accounting flags Cover the new (isAdmin || isAuditor) branch in getSuggestedSearchesVisibility for: - unapprovedCash (Auditor passes, User does not) - unapprovedCard (Auditor passes, User does not) - reconciliation (Auditor passes via Expensify-Card branch) Plus regression checks that User stays excluded and Admin behaviour is unchanged. --- tests/unit/Search/SearchUIUtilsTest.ts | 90 ++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index 64d9b2128bcd..3192f3895f35 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -7769,6 +7769,96 @@ describe('SearchUIUtils', () => { const response = SearchUIUtils.getSuggestedSearchesVisibility(regularEmail, {}, policies, undefined); expect(response.visibility.topSpenders).toBe(false); }); + + test('Should show Unapproved Cash for Auditor role in paid policy with approvals and payments enabled', () => { + const auditorEmail = 'auditor@policy.com'; + const policyKey = `policy_${policyID}`; + + const policies: OnyxCollection = { + [policyKey]: { + id: policyID, + type: CONST.POLICY.TYPE.TEAM, + role: CONST.POLICY.ROLE.AUDITOR, + approvalMode: CONST.POLICY.APPROVAL_MODE.ADVANCED, + } as OnyxTypes.Policy, + }; + + const response = SearchUIUtils.getSuggestedSearchesVisibility(auditorEmail, {}, policies, undefined); + expect(response.visibility.unapprovedCash).toBe(true); + }); + + test('Should show Unapproved Card for Auditor role in paid policy with approvals enabled and a default Expensify card', () => { + const auditorEmail = 'auditor@policy.com'; + const policyKey = `policy_${policyID}`; + + const policies: OnyxCollection = { + [policyKey]: { + id: policyID, + type: CONST.POLICY.TYPE.TEAM, + role: CONST.POLICY.ROLE.AUDITOR, + approvalMode: CONST.POLICY.APPROVAL_MODE.ADVANCED, + } as OnyxTypes.Policy, + }; + + const response = SearchUIUtils.getSuggestedSearchesVisibility(auditorEmail, {}, policies, {} as CardFeedForDisplay); + expect(response.visibility.unapprovedCard).toBe(true); + }); + + test('Should show Reconciliation for Auditor role in paid policy with Expensify Cards enabled', () => { + const auditorEmail = 'auditor@policy.com'; + const policyKey = `policy_${policyID}`; + + const policies: OnyxCollection = { + [policyKey]: { + id: policyID, + type: CONST.POLICY.TYPE.TEAM, + role: CONST.POLICY.ROLE.AUDITOR, + areExpensifyCardsEnabled: true, + } as OnyxTypes.Policy, + }; + + const response = SearchUIUtils.getSuggestedSearchesVisibility(auditorEmail, {}, policies, undefined); + expect(response.visibility.reconciliation).toBe(true); + }); + + test('Should hide Unapproved Cash, Unapproved Card, and Reconciliation for User role even when prerequisites are met', () => { + const userEmail = 'user@policy.com'; + const policyKey = `policy_${policyID}`; + + const policies: OnyxCollection = { + [policyKey]: { + id: policyID, + type: CONST.POLICY.TYPE.TEAM, + role: CONST.POLICY.ROLE.USER, + approvalMode: CONST.POLICY.APPROVAL_MODE.ADVANCED, + areExpensifyCardsEnabled: true, + } as OnyxTypes.Policy, + }; + + const response = SearchUIUtils.getSuggestedSearchesVisibility(userEmail, {}, policies, {} as CardFeedForDisplay); + expect(response.visibility.unapprovedCash).toBe(false); + expect(response.visibility.unapprovedCard).toBe(false); + expect(response.visibility.reconciliation).toBe(false); + }); + + test('Should still show Unapproved Cash, Unapproved Card, and Reconciliation for Admin role when prerequisites are met (regression)', () => { + const policyKey = `policy_${policyID}`; + + const policies: OnyxCollection = { + [policyKey]: { + id: policyID, + type: CONST.POLICY.TYPE.TEAM, + role: CONST.POLICY.ROLE.ADMIN, + approvalMode: CONST.POLICY.APPROVAL_MODE.ADVANCED, + areExpensifyCardsEnabled: true, + } as OnyxTypes.Policy, + }; + + const response = SearchUIUtils.getSuggestedSearchesVisibility(adminEmail, {}, policies, {} as CardFeedForDisplay); + expect(response.visibility.unapprovedCash).toBe(true); + expect(response.visibility.unapprovedCard).toBe(true); + expect(response.visibility.reconciliation).toBe(true); + }); }); describe('Test getSuggestedSearches sort defaults', () => {