diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index a0bb12e50a64..02f4dd3ad451 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -4679,6 +4679,7 @@ export { isDynamicExternalWorkflowSubmitAction, isMarkAsClosedAction, isApprovedAction, + isForwardedAction, isDynamicExternalWorkflowForwardedAction, isUnapprovedAction, isDynamicExternalWorkflowSubmitFailedAction, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index e8f991f82665..5ba7740ff278 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -207,6 +207,7 @@ import { isDynamicExternalWorkflowApproveFailedAction, isDynamicExternalWorkflowSubmitFailedAction, isExportIntegrationAction, + isForwardedAction, isIntegrationMessageAction, isModifiedExpenseAction, isMoneyRequestAction, @@ -2026,6 +2027,17 @@ function requiresManualSubmission(report: OnyxEntry, policy: OnyxEntry

): boolean { + if (!report?.reportID) { + return false; + } + + const reportActions = Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`] ?? {}); + const lastSubmittedAt = reportActions.filter(isSubmittedAction).reduce((latest, action) => (action.created > latest ? action.created : latest), ''); + + return reportActions.some((action) => isForwardedAction(action) && action.created > lastSubmittedAt); +} + function isAwaitingFirstLevelApproval(report: OnyxEntry): boolean { if (!report) { return false; @@ -2034,7 +2046,7 @@ function isAwaitingFirstLevelApproval(report: OnyxEntry): 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); } /** @@ -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; } diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index b5327e413e2c..1c3a9698abf5 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -57,6 +57,7 @@ import { canEditMoneyRequest, canEditReportAction, canEditReportDescription, + canEditReportPolicy, canEditReportTitle, canEditRoomVisibility, canEditWriteCapability, @@ -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'; @@ -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 = { + ...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 = { + [`${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', () => {