diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 3b8fce748f45..66f63ddfd27f 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -12,6 +12,7 @@ import type {EmptyObject} from '@src/types/utils/EmptyObject'; import DateUtils from './DateUtils'; import EmailUtils from './EmailUtils'; import * as PersonalDetailsUtils from './PersonalDetailsUtils'; +import * as PolicyUtils from './PolicyUtils'; import * as ReportUtils from './ReportUtils'; let currentUserAccountID = -1; @@ -81,12 +82,13 @@ function buildNextStep( const {policyID = '', ownerAccountID = -1, managerID = -1} = report; const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] ?? ({} as Policy); - const {submitsTo, harvesting, preventSelfApproval, autoReportingFrequency, autoReportingOffset} = policy; + const {harvesting, preventSelfApproval, autoReportingFrequency, autoReportingOffset} = policy; + const submitToAccountID = PolicyUtils.getSubmitToAccountID(policy, ownerAccountID); const isOwner = currentUserAccountID === ownerAccountID; const isManager = currentUserAccountID === managerID; - const isSelfApproval = currentUserAccountID === submitsTo; + const isSelfApproval = currentUserAccountID === submitToAccountID; const ownerLogin = PersonalDetailsUtils.getLoginsByAccountIDs([ownerAccountID])[0] ?? ''; - const managerDisplayName = isSelfApproval ? 'you' : ReportUtils.getDisplayNameForParticipant(submitsTo) ?? ''; + const managerDisplayName = isSelfApproval ? 'you' : ReportUtils.getDisplayNameForParticipant(submitToAccountID) ?? ''; const type: ReportNextStep['type'] = 'neutral'; let optimisticNextStep: ReportNextStep | null; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index cf6726ef1129..731dc5700c8e 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -13,7 +13,7 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; import getPolicyIDFromState from './Navigation/getPolicyIDFromState'; import Navigation, {navigationRef} from './Navigation/Navigation'; import type {RootStackParamList, State} from './Navigation/types'; -import {getPersonalDetailByEmail} from './PersonalDetailsUtils'; +import {getAccountIDsByLogins, getLoginsByAccountIDs, getPersonalDetailByEmail} from './PersonalDetailsUtils'; type MemberEmailsToAccountIDs = Record; @@ -321,6 +321,38 @@ function isPolicyFeatureEnabled(policy: OnyxEntry | EmptyObject, feature return Boolean(policy?.[featureName]); } +function getApprovalWorkflow(policy: OnyxEntry | EmptyObject): ValueOf { + if (policy?.type === CONST.POLICY.TYPE.PERSONAL) { + return CONST.POLICY.APPROVAL_MODE.OPTIONAL; + } + + return policy?.approvalMode ?? CONST.POLICY.APPROVAL_MODE.ADVANCED; +} + +function getDefaultApprover(policy: OnyxEntry | EmptyObject): string { + return policy?.approver ?? policy?.owner ?? ''; +} + +/** + * Returns the accountID to whom the given employeeAccountID submits reports to in the given Policy. + */ +function getSubmitToAccountID(policy: OnyxEntry | EmptyObject, employeeAccountID: number): number { + const employeeLogin = getLoginsByAccountIDs([employeeAccountID])[0]; + const defaultApprover = getDefaultApprover(policy); + + // For policy using the optional or basic workflow, the manager is the policy default approver. + if (([CONST.POLICY.APPROVAL_MODE.OPTIONAL, CONST.POLICY.APPROVAL_MODE.BASIC] as Array>).includes(getApprovalWorkflow(policy))) { + return getAccountIDsByLogins([defaultApprover])[0]; + } + + const employee = policy?.employeeList?.[employeeLogin]; + if (!employee) { + return -1; + } + + return getAccountIDsByLogins([employee.submitsTo ?? defaultApprover])[0]; +} + function getPersonalPolicy() { return Object.values(allPolicies ?? {}).find((policy) => policy?.type === CONST.POLICY.TYPE.PERSONAL); } @@ -386,6 +418,7 @@ export { getTaxByID, hasPolicyCategoriesError, getPolicyIDFromNavigationState, + getSubmitToAccountID, getAdminEmployees, getPolicy, }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 27cb6863e133..f92106d40b6e 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3482,9 +3482,10 @@ function buildOptimisticExpenseReport(chatReportID: string, policyID: string, pa lastVisibleActionCreated: DateUtils.getDBTime(), }; - // The account defined in the policy submitsTo field is the approver/ manager for this report - if (policy?.submitsTo) { - expenseReport.managerID = policy.submitsTo; + // Get the approver/manager for this report to properly display the optimistic data + const submitToAccountID = PolicyUtils.getSubmitToAccountID(policy, payeeAccountID); + if (submitToAccountID) { + expenseReport.managerID = submitToAccountID; } const titleReportField = getTitleReportField(getReportFieldsByPolicyID(policyID) ?? {}); @@ -6024,9 +6025,9 @@ function isAllowedToApproveExpenseReport(report: OnyxEntry, approverAcco function isAllowedToSubmitDraftExpenseReport(report: OnyxEntry): boolean { const policy = getPolicy(report?.policyID); - const {submitsTo} = policy; + const submitToAccountID = PolicyUtils.getSubmitToAccountID(policy, report?.ownerAccountID ?? -1); - return isAllowedToApproveExpenseReport(report, submitsTo); + return isAllowedToApproveExpenseReport(report, submitToAccountID); } /** diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 7fb31cbed647..84eb0ce27a25 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5612,7 +5612,7 @@ function submitReport(expenseReport: OnyxTypes.Report) { const parameters: SubmitReportParams = { reportID: expenseReport.reportID, - managerAccountID: policy.submitsTo ?? expenseReport.managerID, + managerAccountID: PolicyUtils.getSubmitToAccountID(policy, expenseReport.ownerAccountID ?? -1) ?? expenseReport.managerID, reportActionID: optimisticSubmittedReportAction.reportActionID, }; diff --git a/tests/unit/NextStepUtilsTest.ts b/tests/unit/NextStepUtilsTest.ts index 072a06748da9..65ce7352b6e6 100644 --- a/tests/unit/NextStepUtilsTest.ts +++ b/tests/unit/NextStepUtilsTest.ts @@ -22,7 +22,6 @@ describe('libs/NextStepUtils', () => { // Important props id: policyID, owner: currentUserEmail, - submitsTo: currentUserAccountID, harvesting: { enabled: false, }, @@ -51,6 +50,11 @@ describe('libs/NextStepUtils', () => { login: strangeEmail, avatar: '', }, + [currentUserAccountID]: { + accountID: currentUserAccountID, + login: currentUserEmail, + avatar: '', + }, }, ...policyCollectionDataSet, }).then(waitForBatchedUpdates); @@ -341,8 +345,12 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - submitsTo: currentUserAccountID, preventSelfApproval: true, + employeeList: { + [currentUserEmail]: { + submitsTo: currentUserEmail, + }, + }, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -403,7 +411,11 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - submitsTo: strangeAccountID, + employeeList: { + [currentUserEmail]: { + submitsTo: strangeEmail, + }, + }, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.SUBMITTED); @@ -438,9 +450,17 @@ describe('libs/NextStepUtils', () => { }, ]; - const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.SUBMITTED); + return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + employeeList: { + [strangeEmail]: { + submitsTo: currentUserEmail, + }, + }, + }).then(() => { + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.SUBMITTED); - expect(result).toMatchObject(optimisticNextStep); + expect(result).toMatchObject(optimisticNextStep); + }); }); test('submit and close approval mode', () => {