Skip to content

Commit f63b744

Browse files
authored
Merge pull request #90877 from emkhalid/fix/88788-next-step-approver
Fix incorrect first-submit next-step approver
2 parents 25a7858 + ef8fd19 commit f63b744

2 files changed

Lines changed: 99 additions & 1 deletion

File tree

src/libs/actions/IOU/ReportWorkflow.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import {
6363
isScanning,
6464
isScanningTransaction,
6565
} from '@libs/TransactionUtils';
66+
import {isValidAccountRoute} from '@libs/ValidationUtils';
6667
import CONST from '@src/CONST';
6768
import ONYXKEYS from '@src/ONYXKEYS';
6869
import ROUTES from '@src/ROUTES';
@@ -1311,6 +1312,7 @@ function submitReport({
13111312
const adminAccountID = policy?.role === CONST.POLICY.ROLE.ADMIN ? currentUserAccountIDParam : undefined;
13121313
const parentReport = getReportOrDraftReport(expenseReport.parentReportID);
13131314
const managerID = getSubmitReportManagerAccountID(policy, expenseReport);
1315+
const optimisticNextStepApproverID = !isSubmitAndClosePolicy && managerID !== undefined && isValidAccountRoute(managerID) ? managerID : undefined;
13141316
const isCurrentUserManager = currentUserAccountIDParam === managerID;
13151317
const optimisticSubmittedReportAction = buildOptimisticSubmittedReportAction(
13161318
expenseReport?.total ?? 0,
@@ -1336,6 +1338,7 @@ function submitReport({
13361338
hasViolations,
13371339
isASAPSubmitBetaEnabled,
13381340
isUnapprove: true,
1341+
bypassNextApproverID: optimisticNextStepApproverID,
13391342
});
13401343
const optimisticNextStep = isDEWPolicy
13411344
? null
@@ -1348,6 +1351,7 @@ function submitReport({
13481351
hasViolations,
13491352
isASAPSubmitBetaEnabled,
13501353
isUnapprove: true,
1354+
bypassNextApproverID: optimisticNextStepApproverID,
13511355
});
13521356
const optimisticData: Array<
13531357
OnyxUpdate<typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS | typeof ONYXKEYS.COLLECTION.REPORT | typeof ONYXKEYS.COLLECTION.NEXT_STEP | typeof ONYXKEYS.COLLECTION.REPORT_METADATA>

tests/actions/IOUTest/ReportWorkflowTest.ts

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import * as API from '@src/libs/API';
3030
import DateUtils from '@src/libs/DateUtils';
3131
import ONYXKEYS from '@src/ONYXKEYS';
3232
import ROUTES from '@src/ROUTES';
33-
import type {Policy, Report, ReportNameValuePairs} from '@src/types/onyx';
33+
import type {Policy, Report, ReportNameValuePairs, ReportNextStepDeprecated} from '@src/types/onyx';
3434
import type ReportAction from '@src/types/onyx/ReportAction';
3535
import type {ReportActions} from '@src/types/onyx/ReportAction';
3636
import type {OnyxData} from '@src/types/onyx/Request';
@@ -1381,6 +1381,100 @@ describe('actions/IOU/ReportWorkflow', () => {
13811381
const optimisticReportUpdate = onyxData.optimisticData?.find((update) => update.key === `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`);
13821382
expect((optimisticReportUpdate?.value as Report | undefined)?.managerID).toBe(adminAccountID);
13831383
});
1384+
1385+
it('uses the rule approver in the optimistic next step when the existing report manager is stale', async () => {
1386+
// eslint-disable-next-line rulesdir/no-multiple-api-calls -- Inspecting API.write calls to verify submit payload and optimistic data.
1387+
const apiWriteSpy = jest.spyOn(API, 'write').mockImplementation(() => Promise.resolve());
1388+
const policyID = '1';
1389+
const submitterAccountID = 100;
1390+
const defaultApproverAccountID = 101;
1391+
const ruleApproverAccountID = 102;
1392+
const submitterEmail = 'submitter@example.com';
1393+
const defaultApproverEmail = 'default-approver@example.com';
1394+
const ruleApproverEmail = 'rule-approver@example.com';
1395+
1396+
await Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, {
1397+
[submitterAccountID]: {accountID: submitterAccountID, login: submitterEmail},
1398+
[defaultApproverAccountID]: {accountID: defaultApproverAccountID, login: defaultApproverEmail},
1399+
[ruleApproverAccountID]: {accountID: ruleApproverAccountID, login: ruleApproverEmail},
1400+
});
1401+
1402+
const policy: Policy = {
1403+
...createRandomPolicy(Number(policyID)),
1404+
id: policyID,
1405+
type: CONST.POLICY.TYPE.CORPORATE,
1406+
approvalMode: CONST.POLICY.APPROVAL_MODE.ADVANCED,
1407+
approver: defaultApproverEmail,
1408+
owner: defaultApproverEmail,
1409+
employeeList: {
1410+
[submitterEmail]: {
1411+
email: submitterEmail,
1412+
submitsTo: defaultApproverEmail,
1413+
},
1414+
},
1415+
rules: {
1416+
approvalRules: [
1417+
{
1418+
id: 'travel-rule',
1419+
applyWhen: [
1420+
{
1421+
field: CONST.POLICY.FIELDS.CATEGORY,
1422+
condition: CONST.POLICY.RULE_CONDITIONS.MATCHES,
1423+
value: 'Travel',
1424+
},
1425+
],
1426+
approver: ruleApproverEmail,
1427+
},
1428+
],
1429+
},
1430+
};
1431+
const expenseReport: Report = {
1432+
...createRandomReport(Number(policyID), undefined),
1433+
reportID: '1',
1434+
policyID,
1435+
type: CONST.REPORT.TYPE.EXPENSE,
1436+
ownerAccountID: submitterAccountID,
1437+
managerID: defaultApproverAccountID,
1438+
stateNum: CONST.REPORT.STATE_NUM.OPEN,
1439+
statusNum: CONST.REPORT.STATUS_NUM.OPEN,
1440+
total: 1000,
1441+
currency: CONST.CURRENCY.USD,
1442+
};
1443+
const transaction: Transaction = {
1444+
...createRandomTransaction(1),
1445+
reportID: expenseReport.reportID,
1446+
category: 'Travel',
1447+
};
1448+
1449+
await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transaction);
1450+
await waitForBatchedUpdates();
1451+
1452+
submitReport({
1453+
expenseReport,
1454+
policy,
1455+
currentUserAccountIDParam: submitterAccountID,
1456+
currentUserEmailParam: submitterEmail,
1457+
hasViolations: false,
1458+
isASAPSubmitBetaEnabled: false,
1459+
expenseReportCurrentNextStepDeprecated: undefined,
1460+
userBillingGracePeriodEnds: undefined,
1461+
amountOwed: 0,
1462+
ownerBillingGracePeriodEnd: undefined,
1463+
delegateEmail: undefined,
1464+
});
1465+
1466+
const [, parameters, onyxData] = apiWriteSpy.mock.calls.at(-1) as [unknown, {managerAccountID?: number}, OnyxData<typeof ONYXKEYS.COLLECTION.REPORT>];
1467+
expect(parameters.managerAccountID).toBe(ruleApproverAccountID);
1468+
1469+
const optimisticReportUpdate = onyxData.optimisticData?.find((update) => update.key === `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`);
1470+
expect((optimisticReportUpdate?.value as Report | undefined)?.managerID).toBe(ruleApproverAccountID);
1471+
expect((optimisticReportUpdate?.value as Report | undefined)?.nextStep?.actorAccountID).toBe(ruleApproverAccountID);
1472+
1473+
const optimisticDeprecatedNextStepUpdate = onyxData.optimisticData?.find((update) => update.key === `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`);
1474+
const optimisticDeprecatedNextStep = optimisticDeprecatedNextStepUpdate?.value as ReportNextStepDeprecated | undefined;
1475+
expect(optimisticDeprecatedNextStep?.message?.find((message) => message.type === 'strong')?.text).toBe(ruleApproverEmail);
1476+
});
1477+
13841478
it('keeps the workspace chat outstanding when an admin submits after approver changes', async () => {
13851479
// eslint-disable-next-line rulesdir/no-multiple-api-calls -- Inspecting optimistic parent chat data after submit from workspace chat.
13861480
const apiWriteSpy = jest.spyOn(API, 'write').mockImplementation(() => Promise.resolve());

0 commit comments

Comments
 (0)