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
1 change: 1 addition & 0 deletions src/libs/ReportActionsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4679,6 +4679,7 @@ export {
isDynamicExternalWorkflowSubmitAction,
isMarkAsClosedAction,
isApprovedAction,
isForwardedAction,
isDynamicExternalWorkflowForwardedAction,
isUnapprovedAction,
isDynamicExternalWorkflowSubmitFailedAction,
Expand Down
16 changes: 14 additions & 2 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ import {
isDynamicExternalWorkflowApproveFailedAction,
isDynamicExternalWorkflowSubmitFailedAction,
isExportIntegrationAction,
isForwardedAction,
isIntegrationMessageAction,
isModifiedExpenseAction,
isMoneyRequestAction,
Expand Down Expand Up @@ -2026,6 +2027,17 @@ function requiresManualSubmission(report: OnyxEntry<Report>, policy: OnyxEntry<P
return isManualSubmitEnabled || (isOpenReport(report) && isInstantSubmitEnabled(policy) && isSubmitAndClose(policy));
}

function hasReportBeenForwardedSinceLastSubmit(report: OnyxEntry<Report>): boolean {
if (!report?.reportID) {
return false;
}

const reportActions = Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`] ?? {});
const lastSubmittedAt = reportActions.filter(isSubmittedAction).reduce<string>((latest, action) => (action.created > latest ? action.created : latest), '');

return reportActions.some((action) => isForwardedAction(action) && action.created > lastSubmittedAt);
}

function isAwaitingFirstLevelApproval(report: OnyxEntry<Report>): boolean {
if (!report) {
return false;
Expand All @@ -2034,7 +2046,7 @@ function isAwaitingFirstLevelApproval(report: OnyxEntry<Report>): boolean {
// This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
const submitsToAccountID = getSubmitToAccountID(getPolicy(report.policyID), report);

return isProcessingReport(report) && submitsToAccountID === report.managerID;
return isProcessingReport(report) && submitsToAccountID === report.managerID && !hasReportBeenForwardedSinceLastSubmit(report);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nabi-ebrahimi BTW this change is affecting a lot more than your test steps indicate. Like it is avoiding delete, change workspace etc menu. And from my testing the BE returns error for this actions so this change is justified. But you should carefully see the places that use this function and include all behavior changes in your test steps.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @FitseTLT, thank you for your feedback.

Could you please let me know if recordings from all flows are required as part of the testing process?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So can u update test steps

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your feedback. I’ve updated the test steps. Could you please review them again when you have a chance?

}

/**
Expand Down Expand Up @@ -4717,7 +4729,7 @@ function canEditMoneyRequest(
}

if (reportPolicy?.type === CONST.POLICY.TYPE.CORPORATE && moneyRequestReport && isSubmitted && isCurrentUserSubmitter(moneyRequestReport)) {
const isForwarded = getSubmitToAccountID(reportPolicy, moneyRequestReport) !== moneyRequestReport.managerID;
const isForwarded = getSubmitToAccountID(reportPolicy, moneyRequestReport) !== moneyRequestReport.managerID || hasReportBeenForwardedSinceLastSubmit(moneyRequestReport);
return !isForwarded;
}

Expand Down
96 changes: 95 additions & 1 deletion tests/unit/ReportUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
canEditMoneyRequest,
canEditReportAction,
canEditReportDescription,
canEditReportPolicy,
canEditReportTitle,
canEditRoomVisibility,
canEditWriteCapability,
Expand Down Expand Up @@ -189,7 +190,10 @@ import type {
import type {ErrorFields, Errors, OnyxValueWithOfflineFeedback} from '@src/types/onyx/OnyxCommon';
import type {JoinWorkspaceResolution} from '@src/types/onyx/OriginalMessage';
import type {ACHAccount, PolicyReportField} from '@src/types/onyx/Policy';
import type {Participant, Participants} from '@src/types/onyx/Report';
import type {Participant, Participants, ReportCollectionDataSet} from '@src/types/onyx/Report';
import type {ReportActionsCollectionDataSet} from '@src/types/onyx/ReportAction';
import type {TransactionCollectionDataSet} from '@src/types/onyx/Transaction';
import type CollectionDataSet from '@src/types/utils/CollectionDataSet';
import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet';
import type IconAsset from '@src/types/utils/IconAsset';
import {actionR14932 as mockIOUAction} from '../../__mocks__/reportData/actions';
Expand Down Expand Up @@ -5486,6 +5490,96 @@ describe('ReportUtils', () => {

expect(canEditRequest).toEqual(false);
});

it('should return false for the submitter after a corporate report was forwarded even when the updated workflow points submitsTo at the current manager', async () => {
const reportID = '89012';
const transactionID = '89012-transaction';
const policyID = '89012-policy';
const forwardedToAccountID = 2;

const reportPolicy: Policy = {
id: policyID,
name: 'Advanced approval policy',
role: CONST.POLICY.ROLE.USER,
type: CONST.POLICY.TYPE.CORPORATE,
owner: '',
outputCurrency: CONST.CURRENCY.USD,
isPolicyExpenseChatEnabled: false,
employeeList: {
'lagertha2@vikings.net': {
email: 'lagertha2@vikings.net',
role: CONST.POLICY.ROLE.USER,
submitsTo: 'floki@vikings.net',
},
},
};
const expenseReport: Report = {
...createExpenseReport(Number(reportID)),
reportID,
policyID,
type: CONST.REPORT.TYPE.EXPENSE,
ownerAccountID: currentUserAccountID,
managerID: forwardedToAccountID,
stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
};
const transaction = {
...createRandomTransaction(89012),
transactionID,
reportID,
};
const moneyRequestAction: ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.IOU> = {
...createRandomReportAction(89012),
actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
actorAccountID: currentUserAccountID,
message: [{type: CONST.REPORT.MESSAGE.TYPE.TEXT, text: ''}],
previousMessage: undefined,
originalMessage: {
IOUReportID: reportID,
IOUTransactionID: transactionID,
amount: 5000,
currency: CONST.CURRENCY.USD,
type: CONST.IOU.REPORT_ACTION_TYPE.CREATE,
},
};

const policyCollectionDataSet: CollectionDataSet<typeof ONYXKEYS.COLLECTION.POLICY> = {
[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]: reportPolicy,
};
const reportCollectionDataSet: ReportCollectionDataSet = {
[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: expenseReport,
};
const transactionCollectionDataSet: TransactionCollectionDataSet = {
[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: transaction,
};
const reportActionsCollectionDataSet: ReportActionsCollectionDataSet = {
[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {
submitted: {
...createRandomReportAction(89013),
actionName: CONST.REPORT.ACTIONS.TYPE.SUBMITTED,
created: '2026-04-21 17:00:00',
},
forwarded: {
...createRandomReportAction(89014),
actionName: CONST.REPORT.ACTIONS.TYPE.FORWARDED,
created: '2026-04-21 17:10:00',
},
},
};

await Onyx.multiSet({
[ONYXKEYS.PERSONAL_DETAILS_LIST]: participantsPersonalDetails,
[ONYXKEYS.SESSION]: {email: currentUserEmail, accountID: currentUserAccountID},
...policyCollectionDataSet,
...reportCollectionDataSet,
...transactionCollectionDataSet,
...reportActionsCollectionDataSet,
});
await waitForBatchedUpdates();

expect(canEditReportPolicy(expenseReport, reportPolicy)).toBe(false);
expect(canEditMoneyRequest(moneyRequestAction, transaction, false, expenseReport, reportPolicy)).toBe(false);
});
});

describe('canEditReportAction', () => {
Expand Down
Loading