-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Fix approval chain when the admin bypasses approvers #70283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
95d4111
4069d85
da07184
ea3d8a3
3999783
84221b3
c2a6eac
59741f3
c4cb8d5
51f90e4
d9285e0
2a09607
9d2a8a5
a4a34cc
9d1f5b6
478fb9d
af3d1b8
aea04a4
be361f4
7db3f4a
df79e2c
6da3f0a
4fe8ede
41a6a08
3e84086
18d01e5
b150943
60e21a7
a13a819
4b6dc18
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -185,6 +185,7 @@ | |
| getReportActionMessageText, | ||
| getReportActionText, | ||
| getRetractedMessage, | ||
| getSortedReportActions, | ||
| getTravelUpdateMessage, | ||
| getWorkspaceCurrencyUpdateMessage, | ||
| getWorkspaceFrequencyUpdateMessage, | ||
|
|
@@ -931,7 +932,7 @@ | |
| let conciergeReportID: OnyxEntry<string>; | ||
| Onyx.connect({ | ||
| key: ONYXKEYS.CONCIERGE_REPORT_ID, | ||
| callback: (value) => { | ||
| conciergeReportID = value; | ||
| }, | ||
| }); | ||
|
|
@@ -939,7 +940,7 @@ | |
| const defaultAvatarBuildingIconTestID = 'SvgDefaultAvatarBuilding Icon'; | ||
| Onyx.connect({ | ||
| key: ONYXKEYS.SESSION, | ||
| callback: (value) => { | ||
| // When signed out, val is undefined | ||
| if (!value) { | ||
| return; | ||
|
|
@@ -957,7 +958,7 @@ | |
| let currentUserPersonalDetails: OnyxEntry<PersonalDetails>; | ||
| Onyx.connect({ | ||
| key: ONYXKEYS.PERSONAL_DETAILS_LIST, | ||
| callback: (value) => { | ||
| if (currentUserAccountID) { | ||
| currentUserPersonalDetails = value?.[currentUserAccountID] ?? undefined; | ||
| } | ||
|
|
@@ -969,14 +970,14 @@ | |
| let allReportsDraft: OnyxCollection<Report>; | ||
| Onyx.connect({ | ||
| key: ONYXKEYS.COLLECTION.REPORT_DRAFT, | ||
| waitForCollectionCallback: true, | ||
| callback: (value) => (allReportsDraft = value), | ||
| }); | ||
|
|
||
| let allPolicies: OnyxCollection<Policy>; | ||
| Onyx.connect({ | ||
| key: ONYXKEYS.COLLECTION.POLICY, | ||
| waitForCollectionCallback: true, | ||
| callback: (value) => (allPolicies = value), | ||
| }); | ||
|
|
||
|
|
@@ -984,7 +985,7 @@ | |
| let reportsByPolicyID: ReportByPolicyMap; | ||
| Onyx.connect({ | ||
| key: ONYXKEYS.COLLECTION.REPORT, | ||
| waitForCollectionCallback: true, | ||
| callback: (value) => { | ||
| allReports = value; | ||
| UnreadIndicatorUpdaterHelper().then((module) => { | ||
|
|
@@ -1027,14 +1028,14 @@ | |
| let allBetas: OnyxEntry<Beta[]>; | ||
| Onyx.connect({ | ||
| key: ONYXKEYS.BETAS, | ||
| callback: (value) => (allBetas = value), | ||
| }); | ||
|
|
||
| let allTransactions: OnyxCollection<Transaction> = {}; | ||
| let reportsTransactions: Record<string, Transaction[]> = {}; | ||
| Onyx.connect({ | ||
| key: ONYXKEYS.COLLECTION.TRANSACTION, | ||
| waitForCollectionCallback: true, | ||
| callback: (value) => { | ||
| if (!value) { | ||
| return; | ||
|
|
@@ -1060,7 +1061,7 @@ | |
| let allReportActions: OnyxCollection<ReportActions>; | ||
| Onyx.connect({ | ||
| key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, | ||
| waitForCollectionCallback: true, | ||
| callback: (actions) => { | ||
| if (!actions) { | ||
| return; | ||
|
|
@@ -1073,7 +1074,7 @@ | |
| const allReportMetadataKeyValue: Record<string, ReportMetadata> = {}; | ||
| Onyx.connect({ | ||
| key: ONYXKEYS.COLLECTION.REPORT_METADATA, | ||
| waitForCollectionCallback: true, | ||
| callback: (value) => { | ||
| if (!value) { | ||
| return; | ||
|
|
@@ -4295,6 +4296,13 @@ | |
| // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850 | ||
| // eslint-disable-next-line deprecation/deprecation | ||
| const policy = getPolicy(report?.policyID); | ||
|
|
||
| // If the current user took control, then they are the final approver and we don't have a next approver | ||
| const bypassApprover = getBypassApproverIfTakenControl(report); | ||
| if (bypassApprover === currentUserAccountID) { | ||
| return undefined; | ||
| } | ||
|
|
||
| const approvalChain = getApprovalChain(policy, report); | ||
| const submitToAccountID = getSubmitToAccountID(policy, report); | ||
|
|
||
|
|
@@ -4312,7 +4320,8 @@ | |
|
|
||
| const nextApproverEmail = approvalChain.length === 1 ? approvalChain.at(0) : approvalChain.at(approvalChain.indexOf(currentUserEmail ?? '') + 1); | ||
| if (!nextApproverEmail) { | ||
| return submitToAccountID; | ||
| // If there's no next approver in the chain, return undefined to indicate the current user is the final approver | ||
| return undefined; | ||
| } | ||
|
|
||
| return getAccountIDsByLogins([nextApproverEmail]).at(0); | ||
|
|
@@ -11430,6 +11439,42 @@ | |
| return isPaidGroupPolicyPolicyUtils(newPolicy) && !!newPolicy.role; | ||
| } | ||
|
|
||
| /** | ||
| * Checks if someone took control of the report and if that take control is still valid | ||
| * A take control is invalidated if there's a SUBMITTED action after it | ||
| */ | ||
| function getBypassApproverIfTakenControl(expenseReport: OnyxEntry<Report>): number | null { | ||
| if (!expenseReport?.reportID) { | ||
| return null; | ||
| } | ||
|
|
||
| if (!isProcessingReport(expenseReport)) { | ||
| return null; | ||
| } | ||
|
|
||
| const reportActions = getAllReportActions(expenseReport.reportID); | ||
| if (!reportActions) { | ||
| return null; | ||
| } | ||
|
|
||
| // Sort actions by created timestamp to get chronological order | ||
| const sortedActions = getSortedReportActions(Object.values(reportActions ?? {}), true); | ||
|
|
||
| // Look through actions in reverse chronological order (newest first) | ||
| // If we find a SUBMITTED action, there's no valid take control since any take control would be older | ||
| for (const action of sortedActions) { | ||
| if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED)) { | ||
|
fahimj marked this conversation as resolved.
|
||
| return null; | ||
| } | ||
|
|
||
| if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.TAKE_CONTROL)) { | ||
| return action.actorAccountID ?? null; | ||
| } | ||
| } | ||
|
Comment on lines
+11465
to
+11473
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't have to check the next submitted action after Also, we don't have to check for
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please review my update in line with your feedback |
||
|
|
||
| return null; | ||
| } | ||
|
|
||
| function getApprovalChain(policy: OnyxEntry<Policy>, expenseReport: OnyxEntry<Report>): string[] { | ||
| const approvalChain: string[] = []; | ||
| const fullApprovalChain: string[] = []; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.