From 6196ec2baaf7d02600e65d8682293334ca9cb2d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 17 Feb 2025 15:23:27 +0100 Subject: [PATCH 01/29] fix logic --- src/libs/PolicyUtils.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 5798f45dde72..09932549b32c 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -1308,16 +1308,14 @@ const getDescriptionForPolicyDomainCard = (domainName: string): string => { * i.e. user.submitsTo === their own email. */ function getAllSelfApprovers(policy: OnyxEntry): string[] { - const defaultApprover = policy?.approver ?? policy?.owner; - if (!policy?.employeeList || !defaultApprover) { + if (!policy?.employeeList || !policy?.owner) { return []; } return Object.keys(policy.employeeList).filter((email) => { const employee = policy?.employeeList?.[email] ?? {}; - return employee?.submitsTo === email && employee?.email !== defaultApprover; + return employee?.submitsTo === email; }); } - /** * Checks if the workspace has only one user and if there no approver for the policy. * If so, we can't enable the "Prevent Self Approvals" feature. From e87f5c8053f27df2a6c8db21ae936ad1eb76f91d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 17 Feb 2025 15:24:16 +0100 Subject: [PATCH 02/29] fix logic in case of default approval workflow --- .../workspace/rules/ExpenseReportRulesSection.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 1ec0ee65e956..215da9f701a2 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -52,12 +52,13 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { return; } - if (selfApproversEmails.length === 0) { - setPolicyPreventSelfApproval(policyID, true); - } else { + const selfApproversEmails = getAllSelfApprovers(policy); + if (selfApproversEmails.length > 0) { setIsPreventSelfApprovalsModalVisible(true); + } else { + setPolicyPreventSelfApproval(policyID, true); } - } + } const {currentApprovalWorkflows, defaultWorkflowMembers, usedApproverEmails} = useMemo(() => { if (!policy || !personalDetails) { @@ -74,7 +75,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { return { defaultWorkflowMembers: result.availableMembers, usedApproverEmails: result.usedApproverEmails, - currentApprovalWorkflows: result.approvalWorkflows.filter((workflow) => !workflow.isDefault), + currentApprovalWorkflows: result.approvalWorkflows, }; }, [personalDetails, policy]); @@ -265,14 +266,14 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { isVisible={isPreventSelfApprovalsModalVisible} title={translate('workspace.rules.expenseReportRules.preventSelfApprovalsTitle')} prompt={translate('workspace.rules.expenseReportRules.preventSelfApprovalsModalText', { - managerEmail: policy?.approver ?? '', + managerEmail: policy?.owner ?? '', })} confirmText={translate('workspace.rules.expenseReportRules.preventSelfApprovalsConfirmButton')} cancelText={translate('common.cancel')} onConfirm={() => { setPolicyPreventSelfApproval(policyID, true); - const defaultApprover = policy?.approver ?? policy?.owner; + const defaultApprover = policy?.owner; if (!defaultApprover) { setIsPreventSelfApprovalsModalVisible(false); return; From fadca217b07aaaacaf6bf6b990f2f88fc24078b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 17 Feb 2025 15:30:51 +0100 Subject: [PATCH 03/29] fix style --- src/pages/workspace/rules/ExpenseReportRulesSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 215da9f701a2..66879906c08a 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -58,7 +58,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { } else { setPolicyPreventSelfApproval(policyID, true); } - } + } const {currentApprovalWorkflows, defaultWorkflowMembers, usedApproverEmails} = useMemo(() => { if (!policy || !personalDetails) { From 3678a1e247561994a0b6c21a40e14787af68970d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Mon, 17 Feb 2025 15:38:00 +0100 Subject: [PATCH 04/29] fix lint --- src/pages/workspace/rules/ExpenseReportRulesSection.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 66879906c08a..1995f20992ad 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -52,8 +52,8 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { return; } - const selfApproversEmails = getAllSelfApprovers(policy); - if (selfApproversEmails.length > 0) { + const allSelfApproversEmails = getAllSelfApprovers(policy); + if (allSelfApproversEmails.length > 0) { setIsPreventSelfApprovalsModalVisible(true); } else { setPolicyPreventSelfApproval(policyID, true); From 6135637b48d1d17ebe7391ae2e96f7d8a69865f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 19 Feb 2025 16:01:33 +0100 Subject: [PATCH 05/29] improve wording --- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 4f097d1d4804..9f6cd3b784ad 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -4706,7 +4706,7 @@ const translations = { `Any members currently approving their own expenses will be removed and replaced with the default approver for this workspace (${managerEmail}).`, preventSelfApprovalsConfirmButton: 'Prevent self-approvals', preventSelfApprovalsModalTitle: 'Prevent self-approvals?', - preventSelfApprovalsDisabledSubtitle: "Self approvals can't be enabled until this workspace has at least two members.", + preventSelfApprovalsDisabledSubtitle: "Self approvals can't be enabled until this workspace has at least two approvers.", }, categoryRules: { title: 'Category rules', diff --git a/src/languages/es.ts b/src/languages/es.ts index be175253a1c7..6959d275f57b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -4776,7 +4776,7 @@ const translations = { `Todos los miembros que actualmente estén aprobando sus propios gastos serán eliminados y reemplazados con el aprobador predeterminado de este espacio de trabajo (${managerEmail}).`, preventSelfApprovalsConfirmButton: 'Evitar autoaprobaciones', preventSelfApprovalsModalTitle: '¿Evitar autoaprobaciones?', - preventSelfApprovalsDisabledSubtitle: 'Las aprobaciones propias no pueden habilitarse hasta que este espacio de trabajo tenga al menos dos miembros.', + preventSelfApprovalsDisabledSubtitle: 'Las aprobaciones propias no pueden habilitarse hasta que este espacio de trabajo tenga al menos dos aprobadores.', }, categoryRules: { title: 'Reglas de categoría', From ec34253aa13d78336f9649895f6872ba79ac336b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 19 Feb 2025 16:01:50 +0100 Subject: [PATCH 06/29] improve logic for self approvals --- src/libs/PolicyUtils.ts | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 09932549b32c..8b3e54f13e97 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -41,6 +41,7 @@ import {isOffline as isOfflineNetworkStore} from './Network/NetworkStore'; import {getAccountIDsByLogins, getLoginsByAccountIDs, getPersonalDetailByEmail} from './PersonalDetailsUtils'; import {getAllSortedTransactions, getCategory, getTag} from './TransactionUtils'; import {isPublicDomain} from './ValidationUtils'; +import ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; type MemberEmailsToAccountIDs = Record; @@ -1304,30 +1305,17 @@ const getDescriptionForPolicyDomainCard = (domainName: string): string => { }; /** - * Returns an array of user emails who are currently self-approving: - * i.e. user.submitsTo === their own email. + * Checks if we can enable the "Prevent Self Approvals" feature for the workspace. + * We can enable it if there are more than 1 approver in the workspace. */ -function getAllSelfApprovers(policy: OnyxEntry): string[] { - if (!policy?.employeeList || !policy?.owner) { - return []; - } - return Object.keys(policy.employeeList).filter((email) => { - const employee = policy?.employeeList?.[email] ?? {}; - return employee?.submitsTo === email; - }); -} -/** - * Checks if the workspace has only one user and if there no approver for the policy. - * If so, we can't enable the "Prevent Self Approvals" feature. - */ -function canEnablePreventSelfApprovals(policy: OnyxEntry): boolean { - if (!policy?.employeeList || !policy.approver) { +function canEnablePreventSelfApprovals(approvalWorkflows: ApprovalWorkflow[]): boolean { + if (approvalWorkflows.length === 0) { return false; } - const employeeEmails = Object.keys(policy.employeeList); + const numberOfApproversForDefaultWorkflow = approvalWorkflows.find((workflow) => workflow.isDefault)?.approvers.length; - return employeeEmails.length > 1; + return numberOfApproversForDefaultWorkflow ? numberOfApproversForDefaultWorkflow > 1 : false; } export { @@ -1336,7 +1324,6 @@ export { extractPolicyIDFromPath, escapeTagName, getActivePolicies, - getAllSelfApprovers, getAdminEmployees, getCleanedTagName, getConnectedIntegration, From 22453bc8bfd6a717d6a07b0d11b48de7685c560d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 19 Feb 2025 16:02:10 +0100 Subject: [PATCH 07/29] update logic for self approvals --- .../rules/ExpenseReportRulesSection.tsx | 55 +++++-------------- 1 file changed, 14 insertions(+), 41 deletions(-) diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 1995f20992ad..93051fb54e8b 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -11,7 +11,7 @@ import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import {convertToDisplayString} from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; -import {canEnablePreventSelfApprovals, getAllSelfApprovers, getWorkflowApprovalsUnavailable} from '@libs/PolicyUtils'; +import {canEnablePreventSelfApprovals, getWorkflowApprovalsUnavailable} from '@libs/PolicyUtils'; import {convertPolicyEmployeesToApprovalWorkflows} from '@libs/WorkflowUtils'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; import { @@ -21,7 +21,7 @@ import { setPolicyPreventMemberCreatedTitle, setPolicyPreventSelfApproval, } from '@userActions/Policy/Policy'; -import {updateApprovalWorkflow} from '@userActions/Workflow'; +import {removeApprovalWorkflow} from '@userActions/Workflow'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -43,8 +43,6 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { const autoPayApprovedReportsUnavailable = policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO; const [isPreventSelfApprovalsModalVisible, setIsPreventSelfApprovalsModalVisible] = useState(false); - const isPreventSelfApprovalsDisabled = !canEnablePreventSelfApprovals(policy) && !policy?.preventSelfApproval; - const selfApproversEmails = getAllSelfApprovers(policy); function handleTogglePreventSelfApprovals(isEnabled: boolean) { if (!isEnabled) { @@ -52,8 +50,9 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { return; } - const allSelfApproversEmails = getAllSelfApprovers(policy); - if (allSelfApproversEmails.length > 0) { + const workflowsWithOnlyOneApproverCount = currentApprovalWorkflows?.filter((workflow) => workflow.approvers.length === 1).length; + + if (workflowsWithOnlyOneApproverCount && workflowsWithOnlyOneApproverCount > 0) { setIsPreventSelfApprovalsModalVisible(true); } else { setPolicyPreventSelfApproval(policyID, true); @@ -78,6 +77,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { currentApprovalWorkflows: result.approvalWorkflows, }; }, [personalDetails, policy]); + const isPreventSelfApprovalsDisabled = !canEnablePreventSelfApprovals(currentApprovalWorkflows as ApprovalWorkflow[]) && !policy?.preventSelfApproval; const renderFallbackSubtitle = ({featureName, variant = 'unlock'}: {featureName: string; variant?: 'unlock' | 'enable'}) => { return ( @@ -266,52 +266,25 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { isVisible={isPreventSelfApprovalsModalVisible} title={translate('workspace.rules.expenseReportRules.preventSelfApprovalsTitle')} prompt={translate('workspace.rules.expenseReportRules.preventSelfApprovalsModalText', { - managerEmail: policy?.owner ?? '', + managerEmail: policy?.approver ?? policy?.owner ?? '', })} confirmText={translate('workspace.rules.expenseReportRules.preventSelfApprovalsConfirmButton')} cancelText={translate('common.cancel')} onConfirm={() => { - setPolicyPreventSelfApproval(policyID, true); - - const defaultApprover = policy?.owner; + const defaultApprover = policy?.approver ?? policy?.owner; if (!defaultApprover) { setIsPreventSelfApprovalsModalVisible(false); return; } - currentApprovalWorkflows?.forEach((workflow: ApprovalWorkflow) => { - const oldApprovers = workflow.approvers ?? []; - const approversToRemove = oldApprovers.filter((approver: Approver) => selfApproversEmails.includes(approver?.email)); - const newApprovers = oldApprovers.filter((approver: Approver) => !selfApproversEmails.includes(approver?.email)); - - if (!newApprovers.some((a) => a.email === defaultApprover)) { - newApprovers.unshift({ - email: defaultApprover, - displayName: defaultApprover, - }); - } + const workflowsToRemove = currentApprovalWorkflows?.filter( + (workflow) => !workflow.isDefault && workflow.approvers.length === 1 + ) || []; - const oldMembers = workflow.members ?? []; - const newMembers = oldMembers.map((member: Member) => { - const isSelfApprover = selfApproversEmails.includes(member.email); - return isSelfApprover ? {...member, submitsTo: defaultApprover} : member; - }); - - const newWorkflow = { - ...workflow, - approvers: newApprovers, - availableMembers: [...workflow.members, ...defaultWorkflowMembers], - members: newMembers, - usedApproverEmails, - isDefault: workflow.isDefault ?? false, - action: CONST.APPROVAL_WORKFLOW.ACTION.EDIT, - errors: null, - }; - - const membersToRemove: Member[] = []; - - updateApprovalWorkflow(policyID, newWorkflow, membersToRemove, approversToRemove); + workflowsToRemove.forEach((workflow) => { + removeApprovalWorkflow(policyID, workflow); }); + setPolicyPreventSelfApproval(policyID, true); setIsPreventSelfApprovalsModalVisible(false); }} onCancel={() => setIsPreventSelfApprovalsModalVisible(false)} From 49eac2d59720df43bfacaeb0839a8e47fd9f78f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 19 Feb 2025 16:03:51 +0100 Subject: [PATCH 08/29] fix style --- src/libs/PolicyUtils.ts | 2 +- src/pages/workspace/rules/ExpenseReportRulesSection.tsx | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 8b3e54f13e97..50d14d2c0145 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -9,6 +9,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm'; import type {OnyxInputOrEntry, Policy, PolicyCategories, PolicyEmployeeList, PolicyTagLists, PolicyTags, Report, TaxRate} from '@src/types/onyx'; +import ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; import type {ErrorFields, PendingAction, PendingFields} from '@src/types/onyx/OnyxCommon'; import type { ConnectionLastSync, @@ -41,7 +42,6 @@ import {isOffline as isOfflineNetworkStore} from './Network/NetworkStore'; import {getAccountIDsByLogins, getLoginsByAccountIDs, getPersonalDetailByEmail} from './PersonalDetailsUtils'; import {getAllSortedTransactions, getCategory, getTag} from './TransactionUtils'; import {isPublicDomain} from './ValidationUtils'; -import ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; type MemberEmailsToAccountIDs = Record; diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 93051fb54e8b..8f7f8eb9142e 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -277,9 +277,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { return; } - const workflowsToRemove = currentApprovalWorkflows?.filter( - (workflow) => !workflow.isDefault && workflow.approvers.length === 1 - ) || []; + const workflowsToRemove = currentApprovalWorkflows?.filter((workflow) => !workflow.isDefault && workflow.approvers.length === 1) || []; workflowsToRemove.forEach((workflow) => { removeApprovalWorkflow(policyID, workflow); From 2453425d1755da8687f52b5a7ea85fe42c564cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 19 Feb 2025 16:24:52 +0100 Subject: [PATCH 09/29] fix lint --- src/libs/PolicyUtils.ts | 2 +- .../rules/ExpenseReportRulesSection.tsx | 35 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index cfa062eeb1e9..b51a8436a333 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -9,7 +9,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm'; import type {OnyxInputOrEntry, Policy, PolicyCategories, PolicyEmployeeList, PolicyTagLists, PolicyTags, Report, TaxRate} from '@src/types/onyx'; -import ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; +import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; import type {ErrorFields, PendingAction, PendingFields} from '@src/types/onyx/OnyxCommon'; import type { ConnectionLastSync, diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 8f7f8eb9142e..c244471baf44 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -26,7 +26,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; -import type {Approver, Member} from '@src/types/onyx/ApprovalWorkflow'; type ExpenseReportRulesSectionProps = { policyID: string; @@ -44,22 +43,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { const [isPreventSelfApprovalsModalVisible, setIsPreventSelfApprovalsModalVisible] = useState(false); - function handleTogglePreventSelfApprovals(isEnabled: boolean) { - if (!isEnabled) { - setPolicyPreventSelfApproval(policyID, false); - return; - } - - const workflowsWithOnlyOneApproverCount = currentApprovalWorkflows?.filter((workflow) => workflow.approvers.length === 1).length; - - if (workflowsWithOnlyOneApproverCount && workflowsWithOnlyOneApproverCount > 0) { - setIsPreventSelfApprovalsModalVisible(true); - } else { - setPolicyPreventSelfApproval(policyID, true); - } - } - - const {currentApprovalWorkflows, defaultWorkflowMembers, usedApproverEmails} = useMemo(() => { + const {currentApprovalWorkflows} = useMemo(() => { if (!policy || !personalDetails) { return {}; } @@ -77,6 +61,21 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { currentApprovalWorkflows: result.approvalWorkflows, }; }, [personalDetails, policy]); + + function handleTogglePreventSelfApprovals(isEnabled: boolean) { + if (!isEnabled) { + setPolicyPreventSelfApproval(policyID, false); + return; + } + + const workflowsWithOnlyOneApproverCount = currentApprovalWorkflows?.filter((workflow) => workflow.approvers.length === 1).length; + + if (workflowsWithOnlyOneApproverCount && workflowsWithOnlyOneApproverCount > 0) { + setIsPreventSelfApprovalsModalVisible(true); + } else { + setPolicyPreventSelfApproval(policyID, true); + } + } const isPreventSelfApprovalsDisabled = !canEnablePreventSelfApprovals(currentApprovalWorkflows as ApprovalWorkflow[]) && !policy?.preventSelfApproval; const renderFallbackSubtitle = ({featureName, variant = 'unlock'}: {featureName: string; variant?: 'unlock' | 'enable'}) => { @@ -277,7 +276,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { return; } - const workflowsToRemove = currentApprovalWorkflows?.filter((workflow) => !workflow.isDefault && workflow.approvers.length === 1) || []; + const workflowsToRemove = currentApprovalWorkflows?.filter((workflow) => !workflow.isDefault && workflow.approvers.length === 1) ?? []; workflowsToRemove.forEach((workflow) => { removeApprovalWorkflow(policyID, workflow); From 120d6e0061a949c71d0b2bb0e3316503a1abbe96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 19 Feb 2025 16:32:16 +0100 Subject: [PATCH 10/29] fix lint --- src/pages/workspace/rules/ExpenseReportRulesSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index c244471baf44..0dae8a9a049d 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -76,7 +76,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { setPolicyPreventSelfApproval(policyID, true); } } - const isPreventSelfApprovalsDisabled = !canEnablePreventSelfApprovals(currentApprovalWorkflows as ApprovalWorkflow[]) && !policy?.preventSelfApproval; + const isPreventSelfApprovalsDisabled = currentApprovalWorkflows && !canEnablePreventSelfApprovals(currentApprovalWorkflows) && !policy?.preventSelfApproval; const renderFallbackSubtitle = ({featureName, variant = 'unlock'}: {featureName: string; variant?: 'unlock' | 'enable'}) => { return ( From 8ed72e6e18c519e49bda805d341b348d3f397155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 19 Feb 2025 16:33:06 +0100 Subject: [PATCH 11/29] fix prettier --- src/pages/workspace/rules/ExpenseReportRulesSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 0dae8a9a049d..1356467b620a 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -75,7 +75,7 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { } else { setPolicyPreventSelfApproval(policyID, true); } - } + } const isPreventSelfApprovalsDisabled = currentApprovalWorkflows && !canEnablePreventSelfApprovals(currentApprovalWorkflows) && !policy?.preventSelfApproval; const renderFallbackSubtitle = ({featureName, variant = 'unlock'}: {featureName: string; variant?: 'unlock' | 'enable'}) => { From 45056677dbc5aea76721398af01a41edd6189985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 19 Feb 2025 16:38:23 +0100 Subject: [PATCH 12/29] fix lint --- src/pages/workspace/rules/ExpenseReportRulesSection.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 1356467b620a..3f0b963b0f1d 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -25,7 +25,6 @@ import {removeApprovalWorkflow} from '@userActions/Workflow'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; type ExpenseReportRulesSectionProps = { policyID: string; From acca957e5671b528e0a19e0efb89fb95c7b0c19e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 26 Feb 2025 17:11:00 +0100 Subject: [PATCH 13/29] handle prevent self approvals --- .../WorkspaceWorkflowsApprovalsEditPage.tsx | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index 348fc1b3a299..0417a8a20cea 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -37,6 +37,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true const [approvalWorkflow] = useOnyx(ONYXKEYS.APPROVAL_WORKFLOW); const [initialApprovalWorkflow, setInitialApprovalWorkflow] = useState(); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); + const [preventSelfApprovalError, setPreventSelfApprovalError] = useState(''); const formRef = useRef(null); const updateApprovalWorkflow = useCallback(() => { @@ -48,6 +49,12 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true return; } + // Check if there's only one approver and policy's prevent self approval feature is enabled + if (policy?.preventSelfApproval && approvalWorkflow.approvers.length === 1) { + setPreventSelfApprovalError(translate('workflowsEditApprovalsPage.preventSelfApprovalError')); + return; + } + // We need to remove members and approvers that are no longer in the updated workflow const membersToRemove = initialApprovalWorkflow.members.filter((initialMember) => !approvalWorkflow.members.some((member) => member.email === initialMember.email)); const approversToRemove = initialApprovalWorkflow.approvers.filter((initialApprover) => !approvalWorkflow.approvers.some((approver) => approver.email === initialApprover.email)); @@ -55,7 +62,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true InteractionManager.runAfterInteractions(() => { Workflow.updateApprovalWorkflow(route.params.policyID, approvalWorkflow, membersToRemove, approversToRemove); }); - }, [approvalWorkflow, initialApprovalWorkflow, route.params.policyID]); + }, [approvalWorkflow, initialApprovalWorkflow, route.params.policyID, policy?.preventSelfApproval, translate]); const removeApprovalWorkflow = useCallback(() => { if (!initialApprovalWorkflow) { @@ -115,6 +122,20 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true setInitialApprovalWorkflow(currentApprovalWorkflow); }, [currentApprovalWorkflow, defaultWorkflowMembers, initialApprovalWorkflow, usedApproverEmails]); + // Check for validation error whenever approval workflow changes + // And policy's prevent self approval feature is enabled + useEffect(() => { + if (!approvalWorkflow) { + return; + } + + if (policy?.preventSelfApproval && approvalWorkflow.approvers.length === 1) { + setPreventSelfApprovalError(translate('workflowsEditApprovalsPage.preventSelfApprovalError')); + } else { + setPreventSelfApprovalError(''); + } + }, [approvalWorkflow, policy?.preventSelfApproval, translate]); + return ( { formRef.current?.scrollTo({y: 0, animated: true}); }} From 1d4c2a1c494bb63e40558561240c249958776534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 26 Feb 2025 17:11:10 +0100 Subject: [PATCH 14/29] add keys for workflows prevent self approvals --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index 238d1311bb50..5ad888aad339 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1630,6 +1630,7 @@ const translations = { title: 'Edit approval workflow', deleteTitle: 'Delete approval workflow', deletePrompt: 'Are you sure you want to delete this approval workflow? All members will subsequently follow the default workflow.', + preventSelfApprovalError: 'This workflow requires at least two approvers when "Prevent self-approvals" is enabled. Please add another approver or disable the "Prevent self-approvals" feature in your workspace rules.', }, workflowsExpensesFromPage: { title: 'Expenses from', diff --git a/src/languages/es.ts b/src/languages/es.ts index ac1e32e23fe3..dcc3e187328d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1630,6 +1630,7 @@ const translations = { title: 'Edicion flujo de aprobación', deleteTitle: 'Eliminar flujo de trabajo de aprobación', deletePrompt: '¿Estás seguro de que quieres eliminar este flujo de trabajo de aprobación? Todos los miembros pasarán a usar el flujo de trabajo predeterminado.', + preventSelfApprovalError: 'Este flujo de trabajo requiere al menos dos aprobadores cuando la opción "Prevenir aprobaciones propias" está habilitada. Por favor, añade otro aprobador o deshabilita la función "Prevenir aprobaciones propias" en las reglas de tu espacio de trabajo.', }, workflowsExpensesFromPage: { title: 'Gastos de', From c68f19f174ef8c58851d0f9a686479fff144df54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 26 Feb 2025 17:14:58 +0100 Subject: [PATCH 15/29] fix style --- src/languages/en.ts | 3 ++- src/languages/es.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 5ad888aad339..4a81645bc618 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1630,7 +1630,8 @@ const translations = { title: 'Edit approval workflow', deleteTitle: 'Delete approval workflow', deletePrompt: 'Are you sure you want to delete this approval workflow? All members will subsequently follow the default workflow.', - preventSelfApprovalError: 'This workflow requires at least two approvers when "Prevent self-approvals" is enabled. Please add another approver or disable the "Prevent self-approvals" feature in your workspace rules.', + preventSelfApprovalError: + 'This workflow requires at least two approvers when "Prevent self-approvals" is enabled. Please add another approver or disable the "Prevent self-approvals" feature in your workspace rules.', }, workflowsExpensesFromPage: { title: 'Expenses from', diff --git a/src/languages/es.ts b/src/languages/es.ts index dcc3e187328d..6f8d8d249bee 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1630,7 +1630,8 @@ const translations = { title: 'Edicion flujo de aprobación', deleteTitle: 'Eliminar flujo de trabajo de aprobación', deletePrompt: '¿Estás seguro de que quieres eliminar este flujo de trabajo de aprobación? Todos los miembros pasarán a usar el flujo de trabajo predeterminado.', - preventSelfApprovalError: 'Este flujo de trabajo requiere al menos dos aprobadores cuando la opción "Prevenir aprobaciones propias" está habilitada. Por favor, añade otro aprobador o deshabilita la función "Prevenir aprobaciones propias" en las reglas de tu espacio de trabajo.', + preventSelfApprovalError: + 'Este flujo de trabajo requiere al menos dos aprobadores cuando la opción "Prevenir aprobaciones propias" está habilitada. Por favor, añade otro aprobador o deshabilita la función "Prevenir aprobaciones propias" en las reglas de tu espacio de trabajo.', }, workflowsExpensesFromPage: { title: 'Gastos de', From 32a9de8aafcf8697499c9e51b75e6684c0fbdb97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Wed, 26 Feb 2025 17:43:48 +0100 Subject: [PATCH 16/29] fix lint --- .../WorkspaceWorkflowsApprovalsEditPage.tsx | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index 0417a8a20cea..0864979c5d9f 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -14,12 +14,12 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types'; -import * as PolicyUtils from '@libs/PolicyUtils'; +import {goBackFromInvalidPolicy, isPendingDeletePolicy, isPolicyAdmin} from '@libs/PolicyUtils'; import {convertPolicyEmployeesToApprovalWorkflows} from '@libs/WorkflowUtils'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; -import * as Workflow from '@userActions/Workflow'; +import {clearApprovalWorkflow, removeApprovalWorkflow, setApprovalWorkflow, updateApprovalWorkflow, validateApprovalWorkflow} from '@userActions/Workflow'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -40,12 +40,12 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true const [preventSelfApprovalError, setPreventSelfApprovalError] = useState(''); const formRef = useRef(null); - const updateApprovalWorkflow = useCallback(() => { + const updateApprovalWorkflowCallback = useCallback(() => { if (!approvalWorkflow || !initialApprovalWorkflow) { return; } - if (!Workflow.validateApprovalWorkflow(approvalWorkflow)) { + if (!validateApprovalWorkflow(approvalWorkflow)) { return; } @@ -60,11 +60,11 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true const approversToRemove = initialApprovalWorkflow.approvers.filter((initialApprover) => !approvalWorkflow.approvers.some((approver) => approver.email === initialApprover.email)); Navigation.dismissModal(); InteractionManager.runAfterInteractions(() => { - Workflow.updateApprovalWorkflow(route.params.policyID, approvalWorkflow, membersToRemove, approversToRemove); + updateApprovalWorkflow(route.params.policyID, approvalWorkflow, membersToRemove, approversToRemove); }); }, [approvalWorkflow, initialApprovalWorkflow, route.params.policyID, policy?.preventSelfApproval, translate]); - const removeApprovalWorkflow = useCallback(() => { + const removeApprovalWorkflowCallback = useCallback(() => { if (!initialApprovalWorkflow) { return; } @@ -73,7 +73,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true Navigation.dismissModal(); InteractionManager.runAfterInteractions(() => { // Remove the approval workflow using the initial data as it could be already edited - Workflow.removeApprovalWorkflow(route.params.policyID, initialApprovalWorkflow); + removeApprovalWorkflow(route.params.policyID, initialApprovalWorkflow); }); }, [initialApprovalWorkflow, route.params.policyID]); @@ -99,8 +99,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true }, [personalDetails, policy, route.params.firstApproverEmail]); // eslint-disable-next-line rulesdir/no-negated-variables - const shouldShowNotFoundView = - (isEmptyObject(policy) && !isLoadingReportData) || !PolicyUtils.isPolicyAdmin(policy) || PolicyUtils.isPendingDeletePolicy(policy) || !currentApprovalWorkflow; + const shouldShowNotFoundView = (isEmptyObject(policy) && !isLoadingReportData) || !isPolicyAdmin(policy) || isPendingDeletePolicy(policy) || !currentApprovalWorkflow; // Set the initial approval workflow when the page is loaded useEffect(() => { @@ -109,10 +108,10 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true } if (!currentApprovalWorkflow) { - return Workflow.clearApprovalWorkflow(); + return clearApprovalWorkflow(); } - Workflow.setApprovalWorkflow({ + setApprovalWorkflow({ ...currentApprovalWorkflow, availableMembers: [...currentApprovalWorkflow.members, ...defaultWorkflowMembers], usedApproverEmails, @@ -148,8 +147,8 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true { formRef.current?.scrollTo({y: 0, animated: true}); @@ -183,7 +182,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true setIsDeleteModalVisible(false)} prompt={translate('workflowsEditApprovalsPage.deletePrompt')} confirmText={translate('common.delete')} From bb386400d9ca5408930efb6435cd7cb0ed3e11f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 27 Feb 2025 15:14:30 +0100 Subject: [PATCH 17/29] update error for self approvals in workflows --- src/languages/en.ts | 3 +-- src/languages/es.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 4a81645bc618..835d9af410fe 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1630,8 +1630,7 @@ const translations = { title: 'Edit approval workflow', deleteTitle: 'Delete approval workflow', deletePrompt: 'Are you sure you want to delete this approval workflow? All members will subsequently follow the default workflow.', - preventSelfApprovalError: - 'This workflow requires at least two approvers when "Prevent self-approvals" is enabled. Please add another approver or disable the "Prevent self-approvals" feature in your workspace rules.', + preventSelfApprovalError: "Members can't submit to themselves with prevent self-approvals enabled. Choose a different approver, or amend the workspace rule.", }, workflowsExpensesFromPage: { title: 'Expenses from', diff --git a/src/languages/es.ts b/src/languages/es.ts index 6f8d8d249bee..df2592225533 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1631,7 +1631,7 @@ const translations = { deleteTitle: 'Eliminar flujo de trabajo de aprobación', deletePrompt: '¿Estás seguro de que quieres eliminar este flujo de trabajo de aprobación? Todos los miembros pasarán a usar el flujo de trabajo predeterminado.', preventSelfApprovalError: - 'Este flujo de trabajo requiere al menos dos aprobadores cuando la opción "Prevenir aprobaciones propias" está habilitada. Por favor, añade otro aprobador o deshabilita la función "Prevenir aprobaciones propias" en las reglas de tu espacio de trabajo.', + 'Los miembros no pueden enviarse informes a sí mismos con la opción de evitar aprobaciones propias habilitada. Elige un aprobador diferente o modifica la regla del espacio de trabajo.', }, workflowsExpensesFromPage: { title: 'Gastos de', From 646267dcabd96a81701773d80dc59a88e47c376f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 27 Feb 2025 15:14:39 +0100 Subject: [PATCH 18/29] fix edge case --- .../approvals/WorkspaceWorkflowsApprovalsEditPage.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index 0864979c5d9f..1c9dbcc45735 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -50,7 +50,10 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true } // Check if there's only one approver and policy's prevent self approval feature is enabled - if (policy?.preventSelfApproval && approvalWorkflow.approvers.length === 1) { + // AND the approver is also in the members list (potential self-approval scenario) + if (policy?.preventSelfApproval && + approvalWorkflow.approvers.length === 1 && + approvalWorkflow.members.some(member => member.email === approvalWorkflow.approvers[0].email)) { setPreventSelfApprovalError(translate('workflowsEditApprovalsPage.preventSelfApprovalError')); return; } @@ -128,7 +131,9 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true return; } - if (policy?.preventSelfApproval && approvalWorkflow.approvers.length === 1) { + if (policy?.preventSelfApproval && + approvalWorkflow.approvers.length === 1 && + approvalWorkflow.members.some(member => member.email === approvalWorkflow.approvers[0].email)) { setPreventSelfApprovalError(translate('workflowsEditApprovalsPage.preventSelfApprovalError')); } else { setPreventSelfApprovalError(''); From c9256407bf7981b443972dac62cd37580476465e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 27 Feb 2025 15:15:18 +0100 Subject: [PATCH 19/29] fix prettier --- .../approvals/WorkspaceWorkflowsApprovalsEditPage.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index 1c9dbcc45735..daff7b5d170a 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -51,9 +51,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true // Check if there's only one approver and policy's prevent self approval feature is enabled // AND the approver is also in the members list (potential self-approval scenario) - if (policy?.preventSelfApproval && - approvalWorkflow.approvers.length === 1 && - approvalWorkflow.members.some(member => member.email === approvalWorkflow.approvers[0].email)) { + if (policy?.preventSelfApproval && approvalWorkflow.approvers.length === 1 && approvalWorkflow.members.some((member) => member.email === approvalWorkflow.approvers[0].email)) { setPreventSelfApprovalError(translate('workflowsEditApprovalsPage.preventSelfApprovalError')); return; } @@ -131,9 +129,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true return; } - if (policy?.preventSelfApproval && - approvalWorkflow.approvers.length === 1 && - approvalWorkflow.members.some(member => member.email === approvalWorkflow.approvers[0].email)) { + if (policy?.preventSelfApproval && approvalWorkflow.approvers.length === 1 && approvalWorkflow.members.some((member) => member.email === approvalWorkflow.approvers[0].email)) { setPreventSelfApprovalError(translate('workflowsEditApprovalsPage.preventSelfApprovalError')); } else { setPreventSelfApprovalError(''); From fa56af6847c3426b528006d6b02c17f9639fdf82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Fri, 28 Feb 2025 10:52:07 +0100 Subject: [PATCH 20/29] remove unneeded method --- src/libs/PolicyUtils.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index be127857330f..d5db18ad40c2 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -1322,23 +1322,8 @@ const getDescriptionForPolicyDomainCard = (domainName: string): string => { return domainName; }; -/** - * Checks if we can enable the "Prevent Self Approvals" feature for the workspace. - * We can enable it if there are more than 1 approver in the workspace. - */ -function canEnablePreventSelfApprovals(approvalWorkflows: ApprovalWorkflow[]): boolean { - if (approvalWorkflows.length === 0) { - return false; - } - - const numberOfApproversForDefaultWorkflow = approvalWorkflows.find((workflow) => workflow.isDefault)?.approvers.length; - - return numberOfApproversForDefaultWorkflow ? numberOfApproversForDefaultWorkflow > 1 : false; -} - export { canEditTaxRate, - canEnablePreventSelfApprovals, extractPolicyIDFromPath, escapeTagName, getActivePolicies, From 80049d235da1ff8374c1eb10c651b8a502ec2f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Fri, 28 Feb 2025 10:54:35 +0100 Subject: [PATCH 21/29] revert changes related to prevent self approvals --- .../WorkspaceWorkflowsApprovalsEditPage.tsx | 28 ++----------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index daff7b5d170a..1ec2f67eebd9 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -37,7 +37,6 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true const [approvalWorkflow] = useOnyx(ONYXKEYS.APPROVAL_WORKFLOW); const [initialApprovalWorkflow, setInitialApprovalWorkflow] = useState(); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); - const [preventSelfApprovalError, setPreventSelfApprovalError] = useState(''); const formRef = useRef(null); const updateApprovalWorkflowCallback = useCallback(() => { @@ -49,13 +48,6 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true return; } - // Check if there's only one approver and policy's prevent self approval feature is enabled - // AND the approver is also in the members list (potential self-approval scenario) - if (policy?.preventSelfApproval && approvalWorkflow.approvers.length === 1 && approvalWorkflow.members.some((member) => member.email === approvalWorkflow.approvers[0].email)) { - setPreventSelfApprovalError(translate('workflowsEditApprovalsPage.preventSelfApprovalError')); - return; - } - // We need to remove members and approvers that are no longer in the updated workflow const membersToRemove = initialApprovalWorkflow.members.filter((initialMember) => !approvalWorkflow.members.some((member) => member.email === initialMember.email)); const approversToRemove = initialApprovalWorkflow.approvers.filter((initialApprover) => !approvalWorkflow.approvers.some((approver) => approver.email === initialApprover.email)); @@ -63,7 +55,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true InteractionManager.runAfterInteractions(() => { updateApprovalWorkflow(route.params.policyID, approvalWorkflow, membersToRemove, approversToRemove); }); - }, [approvalWorkflow, initialApprovalWorkflow, route.params.policyID, policy?.preventSelfApproval, translate]); + }, [approvalWorkflow, initialApprovalWorkflow, route.params.policyID]); const removeApprovalWorkflowCallback = useCallback(() => { if (!initialApprovalWorkflow) { @@ -122,20 +114,6 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true setInitialApprovalWorkflow(currentApprovalWorkflow); }, [currentApprovalWorkflow, defaultWorkflowMembers, initialApprovalWorkflow, usedApproverEmails]); - // Check for validation error whenever approval workflow changes - // And policy's prevent self approval feature is enabled - useEffect(() => { - if (!approvalWorkflow) { - return; - } - - if (policy?.preventSelfApproval && approvalWorkflow.approvers.length === 1 && approvalWorkflow.members.some((member) => member.email === approvalWorkflow.approvers[0].email)) { - setPreventSelfApprovalError(translate('workflowsEditApprovalsPage.preventSelfApprovalError')); - } else { - setPreventSelfApprovalError(''); - } - }, [approvalWorkflow, policy?.preventSelfApproval, translate]); - return ( { formRef.current?.scrollTo({y: 0, animated: true}); }} From 972e5a5f491e0afa70e2a54597ccf5169fb2f415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Fri, 28 Feb 2025 11:51:48 +0100 Subject: [PATCH 22/29] remove unneeded translations --- src/languages/en.ts | 6 ------ src/languages/es.ts | 7 ------- 2 files changed, 13 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 835d9af410fe..b20be36faf66 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1630,7 +1630,6 @@ const translations = { title: 'Edit approval workflow', deleteTitle: 'Delete approval workflow', deletePrompt: 'Are you sure you want to delete this approval workflow? All members will subsequently follow the default workflow.', - preventSelfApprovalError: "Members can't submit to themselves with prevent self-approvals enabled. Choose a different approver, or amend the workspace rule.", }, workflowsExpensesFromPage: { title: 'Expenses from', @@ -4718,11 +4717,6 @@ const translations = { unlockFeatureGoToSubtitle: 'Go to', unlockFeatureEnableWorkflowsSubtitle: ({featureName}: FeatureNameParams) => `and enable workflows, then add ${featureName} to unlock this feature.`, enableFeatureSubtitle: ({featureName}: FeatureNameParams) => `and enable ${featureName} to unlock this feature.`, - preventSelfApprovalsModalText: ({managerEmail}: {managerEmail: string}) => - `Any members currently approving their own expenses will be removed and replaced with the default approver for this workspace (${managerEmail}).`, - preventSelfApprovalsConfirmButton: 'Prevent self-approvals', - preventSelfApprovalsModalTitle: 'Prevent self-approvals?', - preventSelfApprovalsDisabledSubtitle: "Self approvals can't be enabled until this workspace has at least two approvers.", }, categoryRules: { title: 'Category rules', diff --git a/src/languages/es.ts b/src/languages/es.ts index df2592225533..c7532197717e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1630,8 +1630,6 @@ const translations = { title: 'Edicion flujo de aprobación', deleteTitle: 'Eliminar flujo de trabajo de aprobación', deletePrompt: '¿Estás seguro de que quieres eliminar este flujo de trabajo de aprobación? Todos los miembros pasarán a usar el flujo de trabajo predeterminado.', - preventSelfApprovalError: - 'Los miembros no pueden enviarse informes a sí mismos con la opción de evitar aprobaciones propias habilitada. Elige un aprobador diferente o modifica la regla del espacio de trabajo.', }, workflowsExpensesFromPage: { title: 'Gastos de', @@ -4789,11 +4787,6 @@ const translations = { unlockFeatureGoToSubtitle: 'Ir a', unlockFeatureEnableWorkflowsSubtitle: ({featureName}: FeatureNameParams) => `y habilita flujos de trabajo, luego agrega ${featureName} para desbloquear esta función.`, enableFeatureSubtitle: ({featureName}: FeatureNameParams) => `y habilita ${featureName} para desbloquear esta función.`, - preventSelfApprovalsModalText: ({managerEmail}: {managerEmail: string}) => - `Todos los miembros que actualmente estén aprobando sus propios gastos serán eliminados y reemplazados con el aprobador predeterminado de este espacio de trabajo (${managerEmail}).`, - preventSelfApprovalsConfirmButton: 'Evitar autoaprobaciones', - preventSelfApprovalsModalTitle: '¿Evitar autoaprobaciones?', - preventSelfApprovalsDisabledSubtitle: 'Las aprobaciones propias no pueden habilitarse hasta que este espacio de trabajo tenga al menos dos aprobadores.', }, categoryRules: { title: 'Reglas de categoría', From 31f24c7791494e9e57af311496eda52980694b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Fri, 28 Feb 2025 11:52:19 +0100 Subject: [PATCH 23/29] remove new logic to keep previous logic --- src/libs/PolicyUtils.ts | 1 - src/pages/workspace/WorkspaceMembersPage.tsx | 8 +- .../rules/ExpenseReportRulesSection.tsx | 155 +++++------------- 3 files changed, 40 insertions(+), 124 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index d5db18ad40c2..6c7e4dd116e5 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -9,7 +9,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm'; import type {OnyxInputOrEntry, Policy, PolicyCategories, PolicyEmployeeList, PolicyTagLists, PolicyTags, Report, TaxRate} from '@src/types/onyx'; -import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow'; import type {ErrorFields, PendingAction, PendingFields} from '@src/types/onyx/OnyxCommon'; import type { ConnectionLastSync, diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index daf6d6d972ab..8f46146ec9c1 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -55,7 +55,7 @@ import {getMemberAccountIDsForWorkspace, isDeletedPolicyEmployee, isExpensifyTea import {getDisplayNameForParticipant} from '@libs/ReportUtils'; import {convertPolicyEmployeesToApprovalWorkflows, updateWorkflowDataOnApproverRemoval} from '@libs/WorkflowUtils'; import {close} from '@userActions/Modal'; -import {dismissAddedWithPrimaryLoginMessages, setPolicyPreventSelfApproval} from '@userActions/Policy/Policy'; +import {dismissAddedWithPrimaryLoginMessages} from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -239,10 +239,8 @@ function WorkspaceMembersPage({personalDetails, route, policy, currentUserPerson return; } - const previousEmployeesCount = Object.keys(policy?.employeeList ?? {}).length; // Remove the admin from the list const accountIDsToRemove = session?.accountID ? selectedEmployees.filter((id) => id !== session.accountID) : selectedEmployees; - const newEmployeesCount = previousEmployeesCount - accountIDsToRemove.length; // Check if any of the account IDs are approvers const hasApprovers = accountIDsToRemove.some((accountID) => isApprover(policy, accountID)); @@ -274,10 +272,6 @@ function WorkspaceMembersPage({personalDetails, route, policy, currentUserPerson setRemoveMembersConfirmModalVisible(false); InteractionManager.runAfterInteractions(() => { removeMembers(accountIDsToRemove, route.params.policyID); - if (newEmployeesCount === 1 && policy?.preventSelfApproval) { - // We can't let the "Prevent Self Approvals" enabled if there's only one workspace user - setPolicyPreventSelfApproval(route.params.policyID, false); - } }); }; diff --git a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx index 3f0b963b0f1d..916b9b941c94 100644 --- a/src/pages/workspace/rules/ExpenseReportRulesSection.tsx +++ b/src/pages/workspace/rules/ExpenseReportRulesSection.tsx @@ -1,6 +1,4 @@ -import React, {useMemo, useState} from 'react'; -import {useOnyx} from 'react-native-onyx'; -import ConfirmModal from '@components/ConfirmModal'; +import React from 'react'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import Section from '@components/Section'; @@ -11,8 +9,7 @@ import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import {convertToDisplayString} from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; -import {canEnablePreventSelfApprovals, getWorkflowApprovalsUnavailable} from '@libs/PolicyUtils'; -import {convertPolicyEmployeesToApprovalWorkflows} from '@libs/WorkflowUtils'; +import {getWorkflowApprovalsUnavailable} from '@libs/PolicyUtils'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; import { enableAutoApprovalOptions, @@ -21,9 +18,7 @@ import { setPolicyPreventMemberCreatedTitle, setPolicyPreventSelfApproval, } from '@userActions/Policy/Policy'; -import {removeApprovalWorkflow} from '@userActions/Workflow'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; type ExpenseReportRulesSectionProps = { @@ -34,49 +29,10 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const policy = usePolicy(policyID); - const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); - const customReportNamesUnavailable = !policy?.areReportFieldsEnabled; - // Auto-approvals and self-approvals are unavailable due to the policy workflows settings const workflowApprovalsUnavailable = getWorkflowApprovalsUnavailable(policy); + const customReportNamesUnavailable = !policy?.areReportFieldsEnabled; const autoPayApprovedReportsUnavailable = policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO; - const [isPreventSelfApprovalsModalVisible, setIsPreventSelfApprovalsModalVisible] = useState(false); - - const {currentApprovalWorkflows} = useMemo(() => { - if (!policy || !personalDetails) { - return {}; - } - - const defaultApprover = policy?.approver ?? policy.owner; - const result = convertPolicyEmployeesToApprovalWorkflows({ - employees: policy.employeeList ?? {}, - defaultApprover, - personalDetails, - }); - - return { - defaultWorkflowMembers: result.availableMembers, - usedApproverEmails: result.usedApproverEmails, - currentApprovalWorkflows: result.approvalWorkflows, - }; - }, [personalDetails, policy]); - - function handleTogglePreventSelfApprovals(isEnabled: boolean) { - if (!isEnabled) { - setPolicyPreventSelfApproval(policyID, false); - return; - } - - const workflowsWithOnlyOneApproverCount = currentApprovalWorkflows?.filter((workflow) => workflow.approvers.length === 1).length; - - if (workflowsWithOnlyOneApproverCount && workflowsWithOnlyOneApproverCount > 0) { - setIsPreventSelfApprovalsModalVisible(true); - } else { - setPolicyPreventSelfApproval(policyID, true); - } - } - const isPreventSelfApprovalsDisabled = currentApprovalWorkflows && !canEnablePreventSelfApprovals(currentApprovalWorkflows) && !policy?.preventSelfApproval; - const renderFallbackSubtitle = ({featureName, variant = 'unlock'}: {featureName: string; variant?: 'unlock' | 'enable'}) => { return ( @@ -136,21 +92,15 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { }, { title: translate('workspace.rules.expenseReportRules.preventSelfApprovalsTitle'), - subtitle: (() => { - if (workflowApprovalsUnavailable) { - return renderFallbackSubtitle({featureName: translate('common.approvals').toLowerCase()}); - } - if (isPreventSelfApprovalsDisabled) { - return translate('workspace.rules.expenseReportRules.preventSelfApprovalsDisabledSubtitle'); - } - return translate('workspace.rules.expenseReportRules.preventSelfApprovalsSubtitle'); - })(), + subtitle: workflowApprovalsUnavailable + ? renderFallbackSubtitle({featureName: translate('common.approvals').toLowerCase()}) + : translate('workspace.rules.expenseReportRules.preventSelfApprovalsSubtitle'), switchAccessibilityLabel: translate('workspace.rules.expenseReportRules.preventSelfApprovalsTitle'), isActive: policy?.preventSelfApproval && !workflowApprovalsUnavailable, - disabled: workflowApprovalsUnavailable || isPreventSelfApprovalsDisabled, - showLockIcon: workflowApprovalsUnavailable || isPreventSelfApprovalsDisabled, + disabled: workflowApprovalsUnavailable, + showLockIcon: workflowApprovalsUnavailable, pendingAction: policy?.pendingFields?.preventSelfApproval, - onToggle: (isEnabled: boolean) => handleTogglePreventSelfApprovals(isEnabled), + onToggle: (isEnabled: boolean) => setPolicyPreventSelfApproval(policyID, isEnabled), }, { title: translate('workspace.rules.expenseReportRules.autoApproveCompliantReportsTitle'), @@ -229,63 +179,36 @@ function ExpenseReportRulesSection({policyID}: ExpenseReportRulesSectionProps) { ]; return ( - <> -
- {optionItems.map(({title, subtitle, isActive, subMenuItems, showLockIcon, disabled, onToggle, pendingAction}, index) => { - const showBorderBottom = index !== optionItems.length - 1; - - return ( - - ); - })} -
- { - const defaultApprover = policy?.approver ?? policy?.owner; - if (!defaultApprover) { - setIsPreventSelfApprovalsModalVisible(false); - return; - } - - const workflowsToRemove = currentApprovalWorkflows?.filter((workflow) => !workflow.isDefault && workflow.approvers.length === 1) ?? []; - - workflowsToRemove.forEach((workflow) => { - removeApprovalWorkflow(policyID, workflow); - }); - setPolicyPreventSelfApproval(policyID, true); - setIsPreventSelfApprovalsModalVisible(false); - }} - onCancel={() => setIsPreventSelfApprovalsModalVisible(false)} - /> - +
+ {optionItems.map(({title, subtitle, isActive, subMenuItems, showLockIcon, disabled, onToggle, pendingAction}, index) => { + const showBorderBottom = index !== optionItems.length - 1; + + return ( + + ); + })} +
); } From a9c2023ba2a390189189522672098544f15aebeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 6 Mar 2025 09:26:47 +0100 Subject: [PATCH 24/29] create `buildOptimisticNextStepForPreventSelfApprovalsEnabled()` --- src/libs/NextStepUtils.ts | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index bee04cba7847..4c4fed82b9fb 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -82,6 +82,34 @@ function getNextApproverDisplayName(report: OnyxEntry, isUnapprove?: boo return getDisplayNameForParticipant({accountID: approverAccountID}) ?? getPersonalDetailsForAccountID(approverAccountID).login; } +function buildOptimisticNextStepForPreventSelfApprovalsEnabled() { + const optimisticNextStep: ReportNextStep = { + type: 'alert', + icon: CONST.NEXT_STEP.ICONS.HOURGLASS, + message: [ + { + text: 'Oops! Looks like you\'re submitting to ', + }, + { + text: 'yourself', + type: 'strong', + }, + { + text: '. Approving your own reports is ', + }, + { + text: 'forbidden', + type: 'strong', + }, + { + text: ' by your policy. Please submit this report to someone else or contact your admin to change the person you submit to.', + }, + ], + }; + + return optimisticNextStep; +} + /** * Generates an optimistic nextStep based on a current report status and other properties. * @@ -392,4 +420,4 @@ function buildNextStep(report: OnyxEntry, predictedNextStatus: ValueOf Date: Thu, 6 Mar 2025 09:29:59 +0100 Subject: [PATCH 25/29] use `buildOptimisticNextStepForPreventSelfApprovalsEnabled()` --- src/components/MoneyReportHeader.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 9c47dd0da547..b45bb2d8c5bd 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -10,6 +10,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {convertToDisplayString} from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; +import {buildOptimisticNextStepForPreventSelfApprovalsEnabled} from '@libs/NextStepUtils'; import {getConnectedIntegration, isPolicyAdmin} from '@libs/PolicyUtils'; import {getOriginalMessage, isDeletedAction, isMoneyRequestAction, isTrackExpenseAction} from '@libs/ReportActionsUtils'; import { @@ -200,7 +201,13 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; const shouldShowStatusBar = hasAllPendingRTERViolations || shouldShowBrokenConnectionViolation || hasOnlyHeldExpenses || hasScanningReceipt || isPayAtEndExpense || hasOnlyPendingTransactions; - const shouldShowNextStep = !isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !shouldShowStatusBar; + + // When offline and prevent self-approval is enabled, we need to show the optimistic next step + const optimisticNextStep = isOffline && policy?.preventSelfApproval + ? buildOptimisticNextStepForPreventSelfApprovalsEnabled() + : nextStep; + + const shouldShowNextStep = !isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!optimisticNextStep?.message?.length && !shouldShowStatusBar; const shouldShowAnyButton = isDuplicate || shouldShowSettlementButton || @@ -492,7 +499,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea /> )} - {shouldShowNextStep && } + {shouldShowNextStep && } {!!statusBarProps && ( Date: Thu, 6 Mar 2025 09:32:09 +0100 Subject: [PATCH 26/29] fix style --- src/components/MoneyReportHeader.tsx | 8 +++----- src/libs/NextStepUtils.ts | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index b45bb2d8c5bd..20dcb8a5b78e 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -201,12 +201,10 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; const shouldShowStatusBar = hasAllPendingRTERViolations || shouldShowBrokenConnectionViolation || hasOnlyHeldExpenses || hasScanningReceipt || isPayAtEndExpense || hasOnlyPendingTransactions; - + // When offline and prevent self-approval is enabled, we need to show the optimistic next step - const optimisticNextStep = isOffline && policy?.preventSelfApproval - ? buildOptimisticNextStepForPreventSelfApprovalsEnabled() - : nextStep; - + const optimisticNextStep = isOffline && policy?.preventSelfApproval ? buildOptimisticNextStepForPreventSelfApprovalsEnabled() : nextStep; + const shouldShowNextStep = !isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!optimisticNextStep?.message?.length && !shouldShowStatusBar; const shouldShowAnyButton = isDuplicate || diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 4c4fed82b9fb..6bb83671585e 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -88,7 +88,7 @@ function buildOptimisticNextStepForPreventSelfApprovalsEnabled() { icon: CONST.NEXT_STEP.ICONS.HOURGLASS, message: [ { - text: 'Oops! Looks like you\'re submitting to ', + text: "Oops! Looks like you're submitting to ", }, { text: 'yourself', From 8bc65f25cb806faf4c2615e611d652f3e46e2b7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 6 Mar 2025 09:48:03 +0100 Subject: [PATCH 27/29] fix --- src/libs/PolicyUtils.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index dd6231462d14..5cdb1c4601a7 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -1325,6 +1325,33 @@ const getDescriptionForPolicyDomainCard = (domainName: string): string => { return domainName; }; +function isPrefferedExporter(policy: Policy) { + const user = getCurrentUserEmail(); + const exporters = [ + policy.connections?.intacct?.config?.export?.exporter, + policy.connections?.netsuite?.options?.config?.exporter, + policy.connections?.netsuiteQuickStart?.config?.exporter, + policy.connections?.quickbooksDesktop?.config?.export?.exporter, + policy.connections?.quickbooksOnline?.config?.export?.exporter, + policy.connections?.xero?.config?.export?.exporter, + ]; + + return exporters.some((exporter) => exporter && exporter === user); +} + +function isAutoSyncEnabled(policy: Policy) { + const values = [ + policy.connections?.intacct?.config?.autoSync?.enabled, + policy.connections?.netsuite?.config?.autoSync?.enabled, + policy.connections?.netsuiteQuickStart?.config?.autoSync?.enabled, + policy.connections?.quickbooksDesktop?.config?.autoSync?.enabled, + policy.connections?.quickbooksOnline?.config?.autoSync?.enabled, + policy.connections?.xero?.config?.autoSync?.enabled, + ]; + + return values.some((value) => !!value); +} + export { canEditTaxRate, extractPolicyIDFromPath, From f6adc07e2a155824dcfc89a9864005af0957d880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 6 Mar 2025 09:52:06 +0100 Subject: [PATCH 28/29] fix --- src/components/MoneyReportHeader.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 466c4fdc7bd7..ab909acb78c8 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -210,8 +210,12 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const shouldShowStatusBar = hasAllPendingRTERViolations || shouldShowBrokenConnectionViolation || hasOnlyHeldExpenses || hasScanningReceipt || isPayAtEndExpense || hasOnlyPendingTransactions; - // When offline and prevent self-approval is enabled, we need to show the optimistic next step - const optimisticNextStep = isOffline && policy?.preventSelfApproval ? buildOptimisticNextStepForPreventSelfApprovalsEnabled() : nextStep; + // When prevent self-approval is enabled, we need to show the optimistic next step + // We should always show this optimistic message for policies with preventSelfApproval + // to avoid any flicker during transitions between online/offline states + const optimisticNextStep = policy?.preventSelfApproval + ? buildOptimisticNextStepForPreventSelfApprovalsEnabled() + : nextStep; const shouldShowNextStep = !isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!optimisticNextStep?.message?.length && !shouldShowStatusBar; const shouldShowAnyButton = From 7346dd3c2cdd9b332acafb26731b0bc5b38bde37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucien=20Akchot=C3=A9?= Date: Thu, 6 Mar 2025 09:53:25 +0100 Subject: [PATCH 29/29] fix style --- src/components/MoneyReportHeader.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index ab909acb78c8..8c7d2d811142 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -213,9 +213,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea // When prevent self-approval is enabled, we need to show the optimistic next step // We should always show this optimistic message for policies with preventSelfApproval // to avoid any flicker during transitions between online/offline states - const optimisticNextStep = policy?.preventSelfApproval - ? buildOptimisticNextStepForPreventSelfApprovalsEnabled() - : nextStep; + const optimisticNextStep = policy?.preventSelfApproval ? buildOptimisticNextStepForPreventSelfApprovalsEnabled() : nextStep; const shouldShowNextStep = !isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!optimisticNextStep?.message?.length && !shouldShowStatusBar; const shouldShowAnyButton =