Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/libs/actions/IOU/ReportWorkflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
isScanning,
isScanningTransaction,
} from '@libs/TransactionUtils';
import {isValidAccountRoute} from '@libs/ValidationUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
Expand Down Expand Up @@ -1311,6 +1312,7 @@ function submitReport({
const adminAccountID = policy?.role === CONST.POLICY.ROLE.ADMIN ? currentUserAccountIDParam : undefined;
const parentReport = getReportOrDraftReport(expenseReport.parentReportID);
const managerID = getSubmitReportManagerAccountID(policy, expenseReport);
const optimisticNextStepApproverID = !isSubmitAndClosePolicy && managerID !== undefined && isValidAccountRoute(managerID) ? managerID : undefined;
const isCurrentUserManager = currentUserAccountIDParam === managerID;
const optimisticSubmittedReportAction = buildOptimisticSubmittedReportAction(
expenseReport?.total ?? 0,
Expand All @@ -1336,6 +1338,7 @@ function submitReport({
hasViolations,
isASAPSubmitBetaEnabled,
isUnapprove: true,
bypassNextApproverID: optimisticNextStepApproverID,
});
const optimisticNextStep = isDEWPolicy
? null
Expand All @@ -1348,6 +1351,7 @@ function submitReport({
hasViolations,
isASAPSubmitBetaEnabled,
isUnapprove: true,
bypassNextApproverID: optimisticNextStepApproverID,
});
const optimisticData: Array<
OnyxUpdate<typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS | typeof ONYXKEYS.COLLECTION.REPORT | typeof ONYXKEYS.COLLECTION.NEXT_STEP | typeof ONYXKEYS.COLLECTION.REPORT_METADATA>
Expand Down
96 changes: 95 additions & 1 deletion tests/actions/IOUTest/ReportWorkflowTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import * as API from '@src/libs/API';
import DateUtils from '@src/libs/DateUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Policy, Report, ReportNameValuePairs} from '@src/types/onyx';
import type {Policy, Report, ReportNameValuePairs, ReportNextStepDeprecated} from '@src/types/onyx';
import type ReportAction from '@src/types/onyx/ReportAction';
import type {ReportActions} from '@src/types/onyx/ReportAction';
import type {OnyxData} from '@src/types/onyx/Request';
Expand Down Expand Up @@ -1381,6 +1381,100 @@ describe('actions/IOU/ReportWorkflow', () => {
const optimisticReportUpdate = onyxData.optimisticData?.find((update) => update.key === `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`);
expect((optimisticReportUpdate?.value as Report | undefined)?.managerID).toBe(adminAccountID);
});

it('uses the rule approver in the optimistic next step when the existing report manager is stale', async () => {
// eslint-disable-next-line rulesdir/no-multiple-api-calls -- Inspecting API.write calls to verify submit payload and optimistic data.
const apiWriteSpy = jest.spyOn(API, 'write').mockImplementation(() => Promise.resolve());
const policyID = '1';
const submitterAccountID = 100;
const defaultApproverAccountID = 101;
const ruleApproverAccountID = 102;
const submitterEmail = 'submitter@example.com';
const defaultApproverEmail = 'default-approver@example.com';
const ruleApproverEmail = 'rule-approver@example.com';

await Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, {
[submitterAccountID]: {accountID: submitterAccountID, login: submitterEmail},
[defaultApproverAccountID]: {accountID: defaultApproverAccountID, login: defaultApproverEmail},
[ruleApproverAccountID]: {accountID: ruleApproverAccountID, login: ruleApproverEmail},
});

const policy: Policy = {
...createRandomPolicy(Number(policyID)),
id: policyID,
type: CONST.POLICY.TYPE.CORPORATE,
approvalMode: CONST.POLICY.APPROVAL_MODE.ADVANCED,
approver: defaultApproverEmail,
owner: defaultApproverEmail,
employeeList: {
[submitterEmail]: {
email: submitterEmail,
submitsTo: defaultApproverEmail,
},
},
rules: {
approvalRules: [
{
id: 'travel-rule',
applyWhen: [
{
field: CONST.POLICY.FIELDS.CATEGORY,
condition: CONST.POLICY.RULE_CONDITIONS.MATCHES,
value: 'Travel',
},
],
approver: ruleApproverEmail,
},
],
},
};
const expenseReport: Report = {
...createRandomReport(Number(policyID), undefined),
reportID: '1',
policyID,
type: CONST.REPORT.TYPE.EXPENSE,
ownerAccountID: submitterAccountID,
managerID: defaultApproverAccountID,
stateNum: CONST.REPORT.STATE_NUM.OPEN,
statusNum: CONST.REPORT.STATUS_NUM.OPEN,
total: 1000,
currency: CONST.CURRENCY.USD,
};
const transaction: Transaction = {
...createRandomTransaction(1),
reportID: expenseReport.reportID,
category: 'Travel',
};

await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transaction);
await waitForBatchedUpdates();

submitReport({
expenseReport,
policy,
currentUserAccountIDParam: submitterAccountID,
currentUserEmailParam: submitterEmail,
hasViolations: false,
isASAPSubmitBetaEnabled: false,
expenseReportCurrentNextStepDeprecated: undefined,
userBillingGracePeriodEnds: undefined,
amountOwed: 0,
ownerBillingGracePeriodEnd: undefined,
delegateEmail: undefined,
});

const [, parameters, onyxData] = apiWriteSpy.mock.calls.at(-1) as [unknown, {managerAccountID?: number}, OnyxData<typeof ONYXKEYS.COLLECTION.REPORT>];
expect(parameters.managerAccountID).toBe(ruleApproverAccountID);

const optimisticReportUpdate = onyxData.optimisticData?.find((update) => update.key === `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`);
expect((optimisticReportUpdate?.value as Report | undefined)?.managerID).toBe(ruleApproverAccountID);
expect((optimisticReportUpdate?.value as Report | undefined)?.nextStep?.actorAccountID).toBe(ruleApproverAccountID);

const optimisticDeprecatedNextStepUpdate = onyxData.optimisticData?.find((update) => update.key === `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`);
const optimisticDeprecatedNextStep = optimisticDeprecatedNextStepUpdate?.value as ReportNextStepDeprecated | undefined;
expect(optimisticDeprecatedNextStep?.message?.find((message) => message.type === 'strong')?.text).toBe(ruleApproverEmail);
});

it('keeps the workspace chat outstanding when an admin submits after approver changes', async () => {
// eslint-disable-next-line rulesdir/no-multiple-api-calls -- Inspecting optimistic parent chat data after submit from workspace chat.
const apiWriteSpy = jest.spyOn(API, 'write').mockImplementation(() => Promise.resolve());
Expand Down
Loading