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
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 {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 All @@ -62,7 +48,7 @@
};

const allPolicies: OnyxCollection<Policy> = {};
Onyx.connect({

Check warning on line 51 in src/libs/actions/Policy/Member.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.POLICY,
callback: (val, key) => {
if (!key) {
Expand All @@ -78,7 +64,7 @@
});

let allReportActions: OnyxCollection<ReportActions>;
Onyx.connect({

Check warning on line 67 in src/libs/actions/Policy/Member.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (actions) => (allReportActions = actions),
Expand All @@ -86,7 +72,7 @@

let sessionEmail = '';
let sessionAccountID = 0;
Onyx.connect({

Check warning on line 75 in src/libs/actions/Policy/Member.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (val) => {
sessionEmail = val?.email ?? '';
Expand All @@ -95,7 +81,7 @@
});

let policyOwnershipChecks: Record<string, PolicyOwnershipChangeChecks>;
Onyx.connect({

Check warning on line 84 in src/libs/actions/Policy/Member.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.POLICY_OWNERSHIP_CHANGE_CHECKS,
callback: (value) => {
policyOwnershipChecks = value ?? {};
Expand Down Expand Up @@ -409,13 +395,7 @@
* 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 @@
// 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 @@
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 @@
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 @@ -203,7 +203,7 @@
};

const allPolicies: OnyxCollection<Policy> = {};
Onyx.connect({

Check warning on line 206 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.POLICY,
callback: (val, key) => {
if (!key) {
Expand All @@ -219,7 +219,7 @@
});

let allReports: OnyxCollection<Report>;
Onyx.connect({

Check warning on line 222 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -228,7 +228,7 @@
});

let allReportActions: OnyxCollection<ReportActions>;
Onyx.connect({

Check warning on line 231 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (actions) => {
Expand All @@ -238,7 +238,7 @@

let sessionEmail = '';
let sessionAccountID = 0;
Onyx.connect({

Check warning on line 241 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (val) => {
sessionEmail = val?.email ?? '';
Expand All @@ -247,13 +247,13 @@
});

let allPersonalDetails: OnyxEntry<PersonalDetailsList>;
Onyx.connect({

Check warning on line 250 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
callback: (val) => (allPersonalDetails = val),
});

let allRecentlyUsedCurrencies: string[];
Onyx.connect({

Check warning on line 256 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.RECENTLY_USED_CURRENCIES,
callback: (val) => (allRecentlyUsedCurrencies = val ?? []),
});
Expand Down Expand Up @@ -5683,13 +5683,18 @@
});
}

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 @@
},
];

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 @@
clearPolicyTitleFieldError,
inviteWorkspaceEmployeesToUber,
setWorkspaceConfirmationCurrency,
getSetPolicyPreventSelfApprovalOnyxData,
};
55 changes: 6 additions & 49 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,12 +150,12 @@ 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 ?? {};
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 updatedEmployeeList = {...previousEmployeeList, ...updatedEmployees};

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
Loading