Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions src/libs/SearchUIUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,7 @@ function getSuggestedSearchesVisibility(
const isPayer = isPolicyPayer(policy, currentUserEmail);
const isAdmin = policy.role === CONST.POLICY.ROLE.ADMIN;
const isAuditor = policy.role === CONST.POLICY.ROLE.AUDITOR;
const isPaymentsAdmin = policy.role === CONST.POLICY.ROLE.PAYMENTS_ADMIN;
const isExporter = policy.exporter === currentUserEmail;

const isSubmittedTo =
Expand All @@ -772,10 +773,10 @@ function getSuggestedSearchesVisibility(
const isPolicyEligibleForApproveSuggestion = isGroupPolicyEligible && isEligibleForApproveSuggestion(policy.approvalMode, isUserApprover, isSubmittedTo);
const isEligibleForExportSuggestion = isExporter && !hasExportError;
const isEligibleForStatementsSuggestion = isPaidPolicy && (hasCardFeed || !!defaultExpensifyCard);
const isEligibleForUnapprovedCashSuggestion = isPaidPolicy && (isAdmin || isAuditor) && isApprovalEnabled && isPaymentEnabled;
const isEligibleForUnapprovedCashSuggestion = isPaidPolicy && (isAdmin || isAuditor || isPaymentsAdmin) && 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 isEligibleForReimbursementsSuggestion = isPaidPolicy && (isAdmin || isAuditor || isPaymentsAdmin) && isPaymentEnabled && hasVBBA && hasReimburser;
Comment thread
flodnv marked this conversation as resolved.
Resolved
const memberCount = Object.keys(policy.employeeList ?? {}).length;
const isEligibleForTopSpendersSuggestion = isPolicyEligibleForTopSpenders(policy, currentUserEmail);
const isEligibleForTopCategoriesSuggestion = isPolicyEligibleForTopCategories(policy);
Expand Down
84 changes: 84 additions & 0 deletions tests/unit/Search/SearchUIUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10115,6 +10115,90 @@ describe('SearchUIUtils', () => {
expect(reconciliationItem?.searchQuery).toContain('withdrawalType:reimbursement');
});

it('should show Cash accruals and Bank reconciliation, but no Card searches, to a Payments Admin', () => {
// Given a Payments Admin on a workspace with approvals, ACH reimbursement, Expensify Cards, and a card feed
const paymentsAdminEmail = 'paymentsadmin@policy.com';
const mockPolicies = {
policy1: {
id: 'policy1',
name: 'Payments Admin Policy',
owner: adminEmail,
outputCurrency: 'USD',
role: CONST.POLICY.ROLE.PAYMENTS_ADMIN,
type: CONST.POLICY.TYPE.CORPORATE,
approvalMode: CONST.POLICY.APPROVAL_MODE.ADVANCED,
areExpensifyCardsEnabled: true,
reimbursementChoice: CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES,
achAccount: {
bankAccountID: 1234,
reimburser: adminEmail,
state: CONST.BANK_ACCOUNT.STATE.OPEN,
accountNumber: '1234567890',
routingNumber: '1234567890',
addressName: 'Test Address',
bankName: 'Test Bank',
},
},
};
const mockCardFeedsByPolicy: Record<string, CardFeedForDisplay[]> = {
policy1: [
{
id: 'card1',
feed: 'Expensify Card' as const,
fundID: 'fund1',
name: 'Test Card Feed',
},
],
};

// When the suggested searches are computed for that Payments Admin
const {visibility, shouldShowExpensifyCard} = SearchUIUtils.getSuggestedSearchesVisibility(paymentsAdminEmail, mockCardFeedsByPolicy, mockPolicies, undefined);
const sections = SearchUIUtils.createTypeMenuSections({
currentUserEmail: paymentsAdminEmail,
currentUserAccountID: adminAccountID,
cardFeedsByPolicy: mockCardFeedsByPolicy,
defaultCardFeed: undefined,
policies: mockPolicies,
savedSearches: {},
isOffline: false,
defaultExpensifyCard: undefined,
draftTransactionIDs: [],
isTrackIntentUser: false,
});
const reconciliationItem = sections.flatMap((section) => section.menuItems).find((item) => item.key === CONST.SEARCH.SEARCH_KEYS.RECONCILIATION);

// Then the role gets the reimbursement searches, while Card accruals and Card reconciliation stay limited to Admins and Auditors
expect(visibility[CONST.SEARCH.SEARCH_KEYS.UNAPPROVED_CASH]).toBe(true);
expect(visibility[CONST.SEARCH.SEARCH_KEYS.RECONCILIATION]).toBe(true);
expect(visibility[CONST.SEARCH.SEARCH_KEYS.UNAPPROVED_CARD]).toBe(false);
expect(shouldShowExpensifyCard).toBe(false);
expect(reconciliationItem?.searchQuery).toContain('withdrawalType:reimbursement');
});

it('should hide Bank reconciliation from a Payments Admin when the workspace has no active business bank account', () => {
// Given a Payments Admin on a workspace that pays without a verified business bank account
const paymentsAdminEmail = 'paymentsadmin@policy.com';
const mockPolicies = {
policy1: {
id: 'policy1',
name: 'Payments Admin Policy',
owner: adminEmail,
outputCurrency: 'USD',
role: CONST.POLICY.ROLE.PAYMENTS_ADMIN,
type: CONST.POLICY.TYPE.CORPORATE,
approvalMode: CONST.POLICY.APPROVAL_MODE.ADVANCED,
reimbursementChoice: CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL,
},
};

// When the suggested searches are computed for that Payments Admin
const {visibility} = SearchUIUtils.getSuggestedSearchesVisibility(paymentsAdminEmail, {}, mockPolicies, undefined);

// Then Cash accruals still shows, but Bank reconciliation needs a bank account to reconcile against
expect(visibility[CONST.SEARCH.SEARCH_KEYS.UNAPPROVED_CASH]).toBe(true);
expect(visibility[CONST.SEARCH.SEARCH_KEYS.RECONCILIATION]).toBe(false);
});

it('should place todo items (submit, approve, pay) inside the Expense reports section', () => {
const mockPolicies = {
policy1: {
Expand Down
Loading