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
12 changes: 4 additions & 8 deletions src/libs/WorkflowUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,11 +262,10 @@ function convertApprovalWorkflowToPolicyEmployees({
continue;
}

const previousPendingAction = previousEmployeeList[approver.email]?.pendingAction;
updatedEmployeeList[approver.email] = {
email: approver.email,
forwardsTo,
pendingAction: previousPendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? previousPendingAction : pendingAction,
pendingAction,
pendingFields: {
forwardsTo: pendingAction,
},
Expand All @@ -282,11 +281,10 @@ function convertApprovalWorkflowToPolicyEmployees({
continue;
}

const previousPendingAction = previousEmployeeList[email]?.pendingAction;
updatedEmployeeList[email] = {
...(updatedEmployeeList[email] ? updatedEmployeeList[email] : {email}),
submitsTo,
pendingAction: previousPendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? previousPendingAction : pendingAction,
pendingAction,
pendingFields: {
submitsTo: pendingAction,
},
Expand All @@ -297,11 +295,10 @@ function convertApprovalWorkflowToPolicyEmployees({
// which will set the submitsTo field to the default approver email on backend.
if (membersToRemove) {
for (const {email} of membersToRemove) {
const previousPendingAction = previousEmployeeList[email]?.pendingAction;
updatedEmployeeList[email] = {
...(updatedEmployeeList[email] ? updatedEmployeeList[email] : {email}),
submitsTo: defaultApprover,
pendingAction: previousPendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? previousPendingAction : pendingAction,
pendingAction,
};
}
}
Expand All @@ -310,11 +307,10 @@ function convertApprovalWorkflowToPolicyEmployees({
// which will reset the forwardsTo on the backend.
if (approversToRemove) {
for (const {email} of approversToRemove) {
const previousPendingAction = previousEmployeeList[email]?.pendingAction;
updatedEmployeeList[email] = {
...(updatedEmployeeList[email] ? updatedEmployeeList[email] : {email}),
forwardsTo: '',
pendingAction: previousPendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? previousPendingAction : pendingAction,
pendingAction,
};
}
}
Expand Down
122 changes: 29 additions & 93 deletions src/libs/actions/Policy/Member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,31 +24,17 @@ import * as PhoneNumber from '@libs/PhoneNumber';
import {getDefaultApprover, isUserPolicyAdmin} from '@libs/PolicyUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import {updateWorkflowDataOnApproverRemoval} from '@libs/WorkflowUtils';
import * as FormActions from '@userActions/FormActions';
import {getRemoveApprovalWorkflowOnyxData, getUpdateApprovalWorkflowOnyxData} from '@userActions/Workflow';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {
ImportedSpreadsheetMemberData,
InvitedEmailsToAccountIDs,
PersonalDetails,
PersonalDetailsList,
Policy,
PolicyEmployee,
PolicyOwnershipChangeChecks,
Report,
ReportAction,
ReportActions,
} from '@src/types/onyx';
import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow';
import type {ImportedSpreadsheetMemberData, InvitedEmailsToAccountIDs, Policy, PolicyEmployee, PolicyOwnershipChangeChecks, Report, ReportAction, ReportActions} from '@src/types/onyx';
import type {PendingAction} from '@src/types/onyx/OnyxCommon';
import type {JoinWorkspaceResolution} from '@src/types/onyx/OriginalMessage';
import type {ApprovalRule} from '@src/types/onyx/Policy';
import type {NotificationPreference, Participant} from '@src/types/onyx/Report';
import type {OnyxData} from '@src/types/onyx/Request';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import {createPolicyExpenseChats, getSetPolicyPreventSelfApprovalOnyxData} from './Policy';
import {createPolicyExpenseChats} from './Policy';

type OnyxDataReturnType = {
optimisticData: OnyxUpdate[];
Expand Down Expand Up @@ -409,13 +395,7 @@ function resetAccountingPreferredExporter(policyID: string, loginList: string[])
* Remove the passed members from the policy employeeList
* Please see https://github.com/Expensify/App/blob/main/README.md#Security for more details
*/
function removeMembers(
policyID: string,
selectedMemberEmails: string[],
policyMemberEmailsToAccountIDs: Record<string, number>,
approvalWorkflows: ApprovalWorkflow[],
allPersonalDetails: OnyxEntry<PersonalDetailsList>,
) {
function removeMembers(policyID: string, selectedMemberEmails: string[], policyMemberEmailsToAccountIDs: Record<string, number>) {
if (selectedMemberEmails.length === 0) {
return;
}
Expand All @@ -427,46 +407,6 @@ function removeMembers(
// eslint-disable-next-line @typescript-eslint/no-deprecated
const policy = getPolicy(policyID);

const optimisticData: OnyxUpdate[] = [];
const successData: OnyxUpdate[] = [];
const failureData: OnyxUpdate[] = [];

// Update approval workflows after member removal
// Check if any of the account IDs are approvers
const hasApprovers = selectedMemberEmails.some((selectedMemberEmail) => isApprover(policy, selectedMemberEmail));
const ownerDetails = allPersonalDetails?.[policy?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID] ?? ({} as PersonalDetails);

if (hasApprovers) {
const ownerEmail = ownerDetails.login;
// eslint-disable-next-line unicorn/no-array-for-each
accountIDs.forEach((accountID) => {
const removedApprover = allPersonalDetails?.[accountID];
if (!removedApprover?.login || !ownerEmail) {
return;
}
const updatedWorkflows = updateWorkflowDataOnApproverRemoval({
approvalWorkflows,
removedApprover,
ownerDetails,
});
// eslint-disable-next-line unicorn/no-array-for-each
updatedWorkflows.forEach((workflow) => {
if (workflow?.removeApprovalWorkflow) {
const {removeApprovalWorkflow, ...updatedWorkflow} = workflow;
const onyxDataForRemoveApprovalWorkflow = getRemoveApprovalWorkflowOnyxData(updatedWorkflow, policy);
optimisticData.push(...(onyxDataForRemoveApprovalWorkflow.optimisticData ?? []));
successData.push(...(onyxDataForRemoveApprovalWorkflow.successData ?? []));
failureData.push(...(onyxDataForRemoveApprovalWorkflow.failureData ?? []));
} else {
const onyxDataForUpdateApprovalWorkflow = getUpdateApprovalWorkflowOnyxData(workflow, [], [], policy);
optimisticData.push(...(onyxDataForUpdateApprovalWorkflow.optimisticData ?? []));
successData.push(...(onyxDataForUpdateApprovalWorkflow.successData ?? []));
failureData.push(...(onyxDataForUpdateApprovalWorkflow.failureData ?? []));
}
});
});
}

const workspaceChats = ReportUtils.getWorkspaceChats(policyID, accountIDs);
const optimisticClosedReportActions = workspaceChats.map(() =>
ReportUtils.buildOptimisticClosedReportAction(sessionEmail, policy?.name ?? '', CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY),
Expand Down Expand Up @@ -537,32 +477,38 @@ function removeMembers(
const approvalRules: ApprovalRule[] = policy?.rules?.approvalRules ?? [];
const optimisticApprovalRules = approvalRules.filter((rule) => !selectedMemberEmails.includes(rule?.approver ?? ''));

optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: policyKey,
value: {
employeeList: optimisticMembersState,
approver: selectedMemberEmails.includes(policy?.approver ?? '') ? policy?.owner : policy?.approver,
rules: {
...(policy?.rules ?? {}),
approvalRules: optimisticApprovalRules,
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: policyKey,
value: {
employeeList: optimisticMembersState,
approver: selectedMemberEmails.includes(policy?.approver ?? '') ? policy?.owner : policy?.approver,
rules: {
...(policy?.rules ?? {}),
approvalRules: optimisticApprovalRules,
},
},
},
});
];
optimisticData.push(...announceRoomMembers.optimisticData, ...adminRoomMembers.optimisticData, ...preferredExporterOnyxData.optimisticData);

successData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: policyKey,
value: {employeeList: successMembersState},
});
const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: policyKey,
value: {employeeList: successMembersState},
},
];
successData.push(...announceRoomMembers.successData, ...adminRoomMembers.successData, ...preferredExporterOnyxData.successData);

failureData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: policyKey,
value: {employeeList: failureMembersState, approver: policy?.approver, rules: policy?.rules},
});
const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: policyKey,
value: {employeeList: failureMembersState, approver: policy?.approver, rules: policy?.rules},
},
];
failureData.push(...announceRoomMembers.failureData, ...adminRoomMembers.failureData, ...preferredExporterOnyxData.failureData);

const pendingChatMembers = ReportUtils.getPendingChatMembers(accountIDs, [], CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
Expand Down Expand Up @@ -701,16 +647,6 @@ function removeMembers(
policyID,
};

// Update "Prevent Self Approvals" after member removal
const previousEmployeesCount = Object.values(policy?.employeeList ?? {}).filter((employee) => employee.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).length;
const remainingEmployeeCount = previousEmployeesCount - accountIDs.length;
if (remainingEmployeeCount === 1 && policy?.preventSelfApproval) {
const onyxDataForSetPolicyPreventSelfApproval = getSetPolicyPreventSelfApprovalOnyxData(policyID, false);
optimisticData.push(...(onyxDataForSetPolicyPreventSelfApproval.optimisticData ?? []));
successData.push(...(onyxDataForSetPolicyPreventSelfApproval.successData ?? []));
failureData.push(...(onyxDataForSetPolicyPreventSelfApproval.failureData ?? []));
}

API.write(WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, {optimisticData, successData, failureData});
}

Expand Down
29 changes: 7 additions & 22 deletions src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5683,13 +5683,18 @@ function setPolicyPreventMemberCreatedTitle(policyID: string, enforced: boolean)
});
}

function getSetPolicyPreventSelfApprovalOnyxData(policyID: string, preventSelfApproval: boolean): OnyxData {
/**
* Call the API to enable or disable self approvals for the reports
* @param policyID - id of the policy to apply the naming pattern to
* @param preventSelfApproval - flag whether to prevent workspace members from approving their own expense reports
*/
function setPolicyPreventSelfApproval(policyID: string, preventSelfApproval: boolean) {
// This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
// eslint-disable-next-line @typescript-eslint/no-deprecated
const policy = getPolicy(policyID);

if (preventSelfApproval === policy?.preventSelfApproval) {
return {};
return;
}

const optimisticData: OnyxUpdate[] = [
Expand Down Expand Up @@ -5734,25 +5739,6 @@ function getSetPolicyPreventSelfApprovalOnyxData(policyID: string, preventSelfAp
},
];

return {optimisticData, failureData, successData};
}

/**
* Call the API to enable or disable self approvals for the reports
* @param policyID - id of the policy to apply the naming pattern to
* @param preventSelfApproval - flag whether to prevent workspace members from approving their own expense reports
*/
function setPolicyPreventSelfApproval(policyID: string, preventSelfApproval: boolean) {
// This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
// eslint-disable-next-line @typescript-eslint/no-deprecated
const policy = getPolicy(policyID);

if (preventSelfApproval === policy?.preventSelfApproval) {
return;
}

const {optimisticData, failureData, successData} = getSetPolicyPreventSelfApprovalOnyxData(policyID, preventSelfApproval);

const parameters: SetPolicyPreventSelfApprovalParams = {
preventSelfApproval,
policyID,
Expand Down Expand Up @@ -6553,5 +6539,4 @@ export {
clearPolicyTitleFieldError,
inviteWorkspaceEmployeesToUber,
setWorkspaceConfirmationCurrency,
getSetPolicyPreventSelfApprovalOnyxData,
};
53 changes: 5 additions & 48 deletions src/libs/actions/Workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import ONYXKEYS from '@src/ONYXKEYS';
import type {ApprovalWorkflowOnyx, PersonalDetailsList, Policy} from '@src/types/onyx';
import type {Approver, Member} from '@src/types/onyx/ApprovalWorkflow';
import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow';
import type {OnyxData} from '@src/types/onyx/Request';
import {isEmptyObject} from '@src/types/utils/EmptyObject';

type SetApprovalWorkflowApproverParams = {
Expand Down Expand Up @@ -83,9 +82,9 @@ function createApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, policy: Onyx
API.write(WRITE_COMMANDS.CREATE_WORKSPACE_APPROVAL, parameters, {optimisticData, failureData, successData});
}

function getUpdateApprovalWorkflowOnyxData(approvalWorkflow: ApprovalWorkflow, membersToRemove: Member[], approversToRemove: Approver[], policy: OnyxEntry<Policy>): OnyxData {
function updateApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, membersToRemove: Member[], approversToRemove: Approver[], policy: OnyxEntry<Policy>) {
if (!policy) {
return {};
return;
}

const previousDefaultApprover = getDefaultApprover(policy);
Expand All @@ -102,7 +101,7 @@ function getUpdateApprovalWorkflowOnyxData(approvalWorkflow: ApprovalWorkflow, m

// If there are no changes to the employees list, we can exit early
if (isEmptyObject(updatedEmployees) && !newDefaultApprover) {
return {};
return;
}

const optimisticData: OnyxUpdate[] = [
Expand Down Expand Up @@ -143,33 +142,6 @@ function getUpdateApprovalWorkflowOnyxData(approvalWorkflow: ApprovalWorkflow, m
},
];

return {optimisticData, failureData, successData};
}

function updateApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, membersToRemove: Member[], approversToRemove: Approver[], policy: OnyxEntry<Policy>) {
if (!policy) {
return;
}

const previousDefaultApprover = getDefaultApprover(policy);
const newDefaultApprover = approvalWorkflow.isDefault ? approvalWorkflow.approvers.at(0)?.email : undefined;
const previousEmployeeList = Object.fromEntries(Object.entries(policy.employeeList ?? {}).map(([key, value]) => [key, {...value, pendingAction: null}]));
const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({
previousEmployeeList,
approvalWorkflow,
type: CONST.APPROVAL_WORKFLOW.TYPE.UPDATE,
membersToRemove,
approversToRemove,
defaultApprover: newDefaultApprover ?? previousDefaultApprover ?? '',
});

// If there are no changes to the employees list, we can exit early
if (isEmptyObject(updatedEmployees) && !newDefaultApprover) {
return;
}

const {optimisticData, failureData, successData} = getUpdateApprovalWorkflowOnyxData(approvalWorkflow, membersToRemove, approversToRemove, policy);

const parameters: UpdateWorkspaceApprovalParams = {
policyID: policy.id,
employees: JSON.stringify(Object.values(updatedEmployees)),
Expand All @@ -178,9 +150,9 @@ function updateApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, membersToRem
API.write(WRITE_COMMANDS.UPDATE_WORKSPACE_APPROVAL, parameters, {optimisticData, failureData, successData});
}

function getRemoveApprovalWorkflowOnyxData(approvalWorkflow: ApprovalWorkflow, policy: OnyxEntry<Policy>): OnyxData {
function removeApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, policy: OnyxEntry<Policy>) {
if (!policy) {
return {};
return;
}

const previousEmployeeList = policy.employeeList ?? {};
Expand Down Expand Up @@ -228,19 +200,6 @@ function getRemoveApprovalWorkflowOnyxData(approvalWorkflow: ApprovalWorkflow, p
},
];

return {optimisticData, failureData, successData};
}

function removeApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, policy: OnyxEntry<Policy>) {
if (!policy) {
return;
}

const previousEmployeeList = Object.fromEntries(Object.entries(policy.employeeList ?? {}).map(([key, value]) => [key, {...value, pendingAction: null}]));
const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({previousEmployeeList, approvalWorkflow, type: CONST.APPROVAL_WORKFLOW.TYPE.REMOVE});

const {optimisticData, failureData, successData} = getRemoveApprovalWorkflowOnyxData(approvalWorkflow, policy);

const parameters: RemoveWorkspaceApprovalParams = {policyID: policy.id, employees: JSON.stringify(Object.values(updatedEmployees))};
API.write(WRITE_COMMANDS.REMOVE_WORKSPACE_APPROVAL, parameters, {optimisticData, failureData, successData});
}
Expand Down Expand Up @@ -363,6 +322,4 @@ export {
clearApprovalWorkflowApprovers,
clearApprovalWorkflow,
validateApprovalWorkflow,
getRemoveApprovalWorkflowOnyxData,
getUpdateApprovalWorkflowOnyxData,
};
Loading