Skip to content
Open
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
18 changes: 18 additions & 0 deletions src/libs/WorkflowUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,23 @@ function getOpenConnectedToPolicyBusinessBankAccounts(bankAccountList: BankAccou
});
}

/**
* Filter out members who are already assigned to a non-default approval workflow.
* This prevents them from appearing in the "Expenses from" picker when creating a new workflow.
*/
function filterAvailableMembersForNewWorkflow(approvalWorkflows: ApprovalWorkflow[], availableMembers: Member[]): Member[] {
const membersInExistingWorkflows = new Set<string>();
for (const workflow of approvalWorkflows) {
if (workflow.isDefault) {
continue;
}
for (const member of workflow.members) {
membersInExistingWorkflows.add(member.email);
}
}
return availableMembers.filter((member) => !membersInExistingWorkflows.has(member.email));
}

/**
* Combine workflow members with available members, deduplicating by email.
*/
Expand All @@ -587,6 +604,7 @@ export {
calculateApprovers,
convertPolicyEmployeesToApprovalWorkflows,
convertApprovalWorkflowToPolicyEmployees,
filterAvailableMembersForNewWorkflow,
getApprovalLimitDescription,
getEligibleExistingBusinessBankAccounts,
getOpenConnectedToPolicyBusinessBankAccounts,
Expand Down
8 changes: 5 additions & 3 deletions src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import {
} from '@libs/PolicyUtils';
import {hasInProgressVBBA} from '@libs/ReimbursementAccountUtils';
import tokenizedSearch from '@libs/tokenizedSearch';
import {convertPolicyEmployeesToApprovalWorkflows, getEligibleExistingBusinessBankAccounts, INITIAL_APPROVAL_WORKFLOW} from '@libs/WorkflowUtils';
import {convertPolicyEmployeesToApprovalWorkflows, filterAvailableMembersForNewWorkflow, getEligibleExistingBusinessBankAccounts, INITIAL_APPROVAL_WORKFLOW} from '@libs/WorkflowUtils';
import type {WorkspaceSplitNavigatorParamList} from '@navigation/types';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import ExpenseReportRulesSection from '@pages/workspace/rules/ExpenseReportRulesSection';
Expand Down Expand Up @@ -100,6 +100,8 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
localeCompare,
});

const availableMembersForNewWorkflow = useMemo(() => filterAvailableMembersForNewWorkflow(approvalWorkflows, availableMembers), [approvalWorkflows, availableMembers]);

const hasValidExistingAccounts = getEligibleExistingBusinessBankAccounts(bankAccountList, policy?.outputCurrency, true).length > 0;

const isAdvanceApproval = (approvalWorkflows.length > 1 || (approvalWorkflows?.at(0)?.approvers ?? []).length > 1) && isControlPolicy(policy);
Expand Down Expand Up @@ -158,7 +160,7 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
const addApprovalAction = useCallback(() => {
setApprovalWorkflow({
...INITIAL_APPROVAL_WORKFLOW,
availableMembers,
availableMembers: availableMembersForNewWorkflow,
usedApproverEmails,
});

Expand All @@ -174,7 +176,7 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
}

Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_EXPENSES_FROM.getRoute(route.params.policyID));
}, [policy, route.params.policyID, availableMembers, usedApproverEmails]);
}, [policy, route.params.policyID, availableMembersForNewWorkflow, usedApproverEmails]);

const filteredApprovalWorkflows =
policy?.approvalMode === CONST.POLICY.APPROVAL_MODE.ADVANCED || policy?.approvalMode === CONST.POLICY.APPROVAL_MODE.DYNAMICEXTERNAL
Expand Down
55 changes: 55 additions & 0 deletions tests/unit/WorkflowUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
calculateApprovers,
convertApprovalWorkflowToPolicyEmployees,
convertPolicyEmployeesToApprovalWorkflows,
filterAvailableMembersForNewWorkflow,
getApprovalLimitDescription,
getOpenConnectedToPolicyBusinessBankAccounts,
mergeWorkflowMembersWithAvailableMembers,
Expand Down Expand Up @@ -1006,6 +1007,60 @@ describe('WorkflowUtils', () => {
});
});

describe('filterAvailableMembersForNewWorkflow', () => {
it('Should return all members when there are no non-default workflows', () => {
const availableMembers = [buildMember(1), buildMember(2), buildMember(3)];
const workflows = [buildWorkflow([1, 2, 3], [1], {isDefault: true})];

const result = filterAvailableMembersForNewWorkflow(workflows, availableMembers);

expect(result).toEqual(availableMembers);
});

it('Should filter out members already in a non-default workflow', () => {
const availableMembers = [buildMember(1), buildMember(2), buildMember(3)];
const workflows = [buildWorkflow([1, 2, 3], [1], {isDefault: true}), buildWorkflow([2], [4])];

const result = filterAvailableMembersForNewWorkflow(workflows, availableMembers);

expect(result).toEqual([buildMember(1), buildMember(3)]);
});

it('Should filter out members from multiple non-default workflows', () => {
const availableMembers = [buildMember(1), buildMember(2), buildMember(3), buildMember(4)];
const workflows = [buildWorkflow([1, 2, 3, 4], [1], {isDefault: true}), buildWorkflow([1], [5]), buildWorkflow([3], [6])];

const result = filterAvailableMembersForNewWorkflow(workflows, availableMembers);

expect(result).toEqual([buildMember(2), buildMember(4)]);
});

it('Should return all members when workflows list is empty', () => {
const availableMembers = [buildMember(1), buildMember(2)];

const result = filterAvailableMembersForNewWorkflow([], availableMembers);

expect(result).toEqual(availableMembers);
});

it('Should return empty array when all members are in non-default workflows', () => {
const availableMembers = [buildMember(1), buildMember(2)];
const workflows = [buildWorkflow([1, 2], [3])];

const result = filterAvailableMembersForNewWorkflow(workflows, availableMembers);

expect(result).toEqual([]);
});

it('Should return empty array when available members list is empty', () => {
const workflows = [buildWorkflow([1], [2])];

const result = filterAvailableMembersForNewWorkflow(workflows, []);

expect(result).toEqual([]);
});
});

describe('getOpenConnectedToPolicyBusinessBankAccounts', () => {
const matchingBankAccountID = 12345;

Expand Down
Loading