diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts
index 6572b18876143..3338210b76d19 100644
--- a/src/libs/NextStepUtils.ts
+++ b/src/libs/NextStepUtils.ts
@@ -10,6 +10,7 @@ import type {Message} from '@src/types/onyx/ReportNextStep';
import type DeepValueOf from '@src/types/utils/DeepValueOf';
import DateUtils from './DateUtils';
import EmailUtils from './EmailUtils';
+import * as PersonalDetailsUtils from './PersonalDetailsUtils';
import * as PolicyUtils from './PolicyUtils';
import * as ReportUtils from './ReportUtils';
@@ -59,6 +60,20 @@ function parseMessage(messages: Message[] | undefined) {
return `${formattedHtml}`;
}
+function getNextApproverDisplayName(policy: Policy, ownerAccountID: number, submitToAccountID: number, report: OnyxEntry) {
+ const approvalChain = ReportUtils.getApprovalChain(policy, ownerAccountID, report?.total ?? 0);
+ if (approvalChain.length === 0) {
+ return ReportUtils.getDisplayNameForParticipant(submitToAccountID);
+ }
+
+ const nextApproverEmail = approvalChain.length === 1 ? approvalChain[0] : approvalChain[approvalChain.indexOf(currentUserEmail) + 1];
+ if (!nextApproverEmail) {
+ return ReportUtils.getDisplayNameForParticipant(submitToAccountID);
+ }
+
+ return PersonalDetailsUtils.getPersonalDetailByEmail(nextApproverEmail)?.displayName ?? nextApproverEmail;
+}
+
/**
* Generates an optimistic nextStep based on a current report status and other properties.
*
@@ -78,7 +93,8 @@ function buildNextStep(report: OnyxEntry, predictedNextStatus: ValueOf, predictedNextStatus: ValueOf, employeeAccountID: numb
return getAccountIDsByLogins([employee.submitsTo ?? defaultApprover])[0];
}
+function getSubmitToEmail(policy: OnyxEntry, employeeAccountID: number): string {
+ const submitToAccountID = getSubmitToAccountID(policy, employeeAccountID);
+ return getLoginsByAccountIDs([submitToAccountID])[0] ?? '';
+}
+
+/**
+ * Returns the email of the account to forward the report to depending on the approver's approval limit.
+ * Used for advanced approval mode only.
+ */
+function getForwardsToAccount(policy: OnyxEntry, employeeEmail: string, reportTotal: number): string {
+ if (!isControlOnAdvancedApprovalMode(policy)) {
+ return '';
+ }
+
+ const employee = policy?.employeeList?.[employeeEmail];
+ if (!employee) {
+ return '';
+ }
+
+ const positiveReportTotal = Math.abs(reportTotal);
+ if (employee.approvalLimit && employee.overLimitForwardsTo && positiveReportTotal > employee.approvalLimit) {
+ return employee.overLimitForwardsTo;
+ }
+ return employee.forwardsTo ?? '';
+}
+
/**
* Returns the accountID of the policy reimburser, if not available — falls back to the policy owner.
*/
@@ -916,7 +942,6 @@ export {
getPolicyBrickRoadIndicatorStatus,
getPolicyEmployeeListByIdWithoutCurrentUser,
getSortedTagKeys,
- getSubmitToAccountID,
getTagList,
getTagListName,
getTagLists,
@@ -984,6 +1009,8 @@ export {
getIntegrationLastSuccessfulDate,
getCurrentConnectionName,
getCustomersOrJobsLabelNetSuite,
+ getDefaultApprover,
+ getApprovalWorkflow,
getReimburserAccountID,
isControlPolicy,
isNetSuiteCustomSegmentRecord,
@@ -994,6 +1021,9 @@ export {
getCurrentTaxID,
areSettingsInErrorFields,
settingsPendingAction,
+ getSubmitToEmail,
+ getForwardsToAccount,
+ getSubmitToAccountID,
};
export type {MemberEmailsToAccountIDs};
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 64360b0b7e366..0242a07b01854 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -7483,6 +7483,23 @@ function isExported(reportActions: OnyxEntry) {
return Object.values(reportActions).some((action) => ReportActionsUtils.isExportIntegrationAction(action));
}
+function getApprovalChain(policy: OnyxEntry, employeeAccountID: number, reportTotal: number): string[] {
+ const approvalChain: string[] = [];
+
+ // If the policy is not on advanced approval mode, we should not use the approval chain even if it exists.
+ if (!PolicyUtils.isControlOnAdvancedApprovalMode(policy)) {
+ return approvalChain;
+ }
+
+ let nextApproverEmail = PolicyUtils.getSubmitToEmail(policy, employeeAccountID);
+
+ while (nextApproverEmail && !approvalChain.includes(nextApproverEmail)) {
+ approvalChain.push(nextApproverEmail);
+ nextApproverEmail = PolicyUtils.getForwardsToAccount(policy, nextApproverEmail, reportTotal);
+ }
+ return approvalChain;
+}
+
export {
addDomainToShortMention,
completeShortMention,
@@ -7778,6 +7795,7 @@ export {
getReport,
getReportNameValuePairs,
hasReportViolations,
+ getApprovalChain,
isIndividualInvoiceRoom,
};
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index 9549bc23a9d87..24c1112ad904b 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -6829,6 +6829,13 @@ function hasIOUToApproveOrPay(chatReport: OnyxEntry, excludedI
});
}
+function isLastApprover(approvalChain: string[]): boolean {
+ if (approvalChain.length === 0) {
+ return true;
+ }
+ return approvalChain[approvalChain.length - 1] === currentUserEmail;
+}
+
function approveMoneyRequest(expenseReport: OnyxEntry, full?: boolean) {
if (expenseReport?.policyID && SubscriptionUtils.shouldRestrictUserBillableActions(expenseReport.policyID)) {
Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(expenseReport.policyID));
@@ -6842,7 +6849,13 @@ function approveMoneyRequest(expenseReport: OnyxEntry, full?:
total = expenseReport?.unheldTotal;
}
const optimisticApprovedReportAction = ReportUtils.buildOptimisticApprovedReportAction(total, expenseReport?.currency ?? '', expenseReport?.reportID ?? '-1');
- const optimisticNextStep = NextStepUtils.buildNextStep(expenseReport, CONST.REPORT.STATUS_NUM.APPROVED);
+
+ const approvalChain = ReportUtils.getApprovalChain(PolicyUtils.getPolicy(expenseReport?.policyID), expenseReport?.ownerAccountID ?? -1, expenseReport?.total ?? 0);
+
+ const predictedNextStatus = isLastApprover(approvalChain) ? CONST.REPORT.STATUS_NUM.APPROVED : CONST.REPORT.STATUS_NUM.SUBMITTED;
+ const predictedNextState = isLastApprover(approvalChain) ? CONST.REPORT.STATE_NUM.APPROVED : CONST.REPORT.STATE_NUM.SUBMITTED;
+
+ const optimisticNextStep = NextStepUtils.buildNextStep(expenseReport, predictedNextStatus);
const chatReport = getReportOrDraftReport(expenseReport?.chatReportID);
const optimisticReportActionsData: OnyxUpdate = {
@@ -6862,8 +6875,8 @@ function approveMoneyRequest(expenseReport: OnyxEntry, full?:
...expenseReport,
lastMessageText: ReportActionsUtils.getReportActionText(optimisticApprovedReportAction),
lastMessageHtml: ReportActionsUtils.getReportActionHtml(optimisticApprovedReportAction),
- stateNum: CONST.REPORT.STATE_NUM.APPROVED,
- statusNum: CONST.REPORT.STATUS_NUM.APPROVED,
+ stateNum: predictedNextState,
+ statusNum: predictedNextStatus,
pendingFields: {
partial: full ? null : CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},