From 779c68be226c495b8f80935acebba9bfa0c64392 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 10 Jun 2025 14:12:06 -0600 Subject: [PATCH 1/8] revert the revert --- ...ngeReportPolicyAndInviteSubmitterParams.ts | 9 + src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/ReportUtils.ts | 210 ++++++++++++++ src/libs/actions/Policy/Policy.ts | 4 + src/libs/actions/Report.ts | 268 ++++-------------- src/pages/ReportChangeWorkspacePage.tsx | 11 +- 7 files changed, 288 insertions(+), 217 deletions(-) create mode 100644 src/libs/API/parameters/ChangeReportPolicyAndInviteSubmitterParams.ts diff --git a/src/libs/API/parameters/ChangeReportPolicyAndInviteSubmitterParams.ts b/src/libs/API/parameters/ChangeReportPolicyAndInviteSubmitterParams.ts new file mode 100644 index 000000000000..55a546124116 --- /dev/null +++ b/src/libs/API/parameters/ChangeReportPolicyAndInviteSubmitterParams.ts @@ -0,0 +1,9 @@ +type ChangeReportPolicyParams = { + reportID: string; + policyID: string; + reportPreviewReportActionID: string; + changePolicyReportActionID: string; + policyExpenseChatReportID: string; + policyExpenseCreatedReportActionID: string; +}; +export default ChangeReportPolicyParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index c06d5f969550..97be7b690980 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -392,6 +392,7 @@ export type {default as SaveCorpayOnboardingDirectorInformationParams} from './S export type {default as MoveIOUReportToPolicyAndInviteSubmitterParams} from './MoveIOUReportToPolicyAndInviteSubmitterParams'; export type {default as MoveIOUReportToExistingPolicyParams} from './MoveIOUReportToExistingPolicyParams'; export type {default as ChangeReportPolicyParams} from './ChangeReportPolicyParams'; +export type {default as ChangeReportPolicyAndInviteSubmitterParams} from './ChangeReportPolicyAndInviteSubmitterParams'; export type {ChangeTransactionsReportParams, TransactionThreadInfo} from './ChangeTransactionsReportParams'; export type {default as ResetBankAccountSetupParams} from './ResetBankAccountSetupParams'; export type {default as SetPolicyProhibitedExpensesParams} from './SetPolicyProhibitedExpensesParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index aab6464fd315..607c4add5c67 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -478,6 +478,7 @@ const WRITE_COMMANDS = { ADD_WORK_EMAIL: 'AddWorkEmail', SAVE_CORPAY_ONBOARDING_DIRECTOR_INFORMATION: 'SaveCorpayOnboardingDirectorInformation', CHANGE_REPORT_POLICY: 'ChangeReportPolicy', + CHANGE_REPORT_POLICY_AND_INVITE_SUBMITTER: 'ChangeReportPolicyAndInviteSubmitter', CHANGE_TRANSACTIONS_REPORT: 'ChangeTransactionsReport', RETRACT_REPORT: 'RetractReport', PAY_AND_DOWNGRADE: 'PayAndDowngrade', @@ -983,6 +984,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.MERGE_WITH_VALIDATE_CODE]: Parameters.MergeWithValidateCodeParams; // Change report policy [WRITE_COMMANDS.CHANGE_REPORT_POLICY]: Parameters.ChangeReportPolicyParams; + [WRITE_COMMANDS.CHANGE_REPORT_POLICY_AND_INVITE_SUBMITTER]: Parameters.ChangeReportPolicyAndInviteSubmitterParams; [WRITE_COMMANDS.PAY_AND_DOWNGRADE]: null; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c90c66758f8b..4246400030f1 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -9623,6 +9623,215 @@ function createDraftTransactionAndNavigateToParticipantSelector( return createDraftWorkspaceAndNavigateToConfirmationScreen(transactionID, actionName); } +/** + * @private + * Builds a map of parentReportID to child report IDs for efficient traversal. + */ +function buildReportIDToThreadsReportIDsMap(): Record { + const reportIDToThreadsReportIDsMap: Record = {}; + Object.values(allReports ?? {}).forEach((report) => { + if (!report?.parentReportID) { + return; + } + if (!reportIDToThreadsReportIDsMap[report.parentReportID]) { + reportIDToThreadsReportIDsMap[report.parentReportID] = []; + } + reportIDToThreadsReportIDsMap[report.parentReportID].push(report.reportID); + }); + return reportIDToThreadsReportIDsMap; +} + +/** + * @private + * Recursively updates the policyID for a report and all its child reports. + */ +function updatePolicyIdForReportAndThreads( + currentReportID: string, + policyID: string, + reportIDToThreadsReportIDsMap: Record, + optimisticData: OnyxUpdate[], + failureData: OnyxUpdate[], +) { + const currentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${currentReportID}`]; + const originalPolicyID = currentReport?.policyID; + + if (originalPolicyID) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${currentReportID}`, + value: {policyID}, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${currentReportID}`, + value: {policyID: originalPolicyID}, + }); + } + + // Recursively process child reports for the current report + const childReportIDs = reportIDToThreadsReportIDsMap[currentReportID] || []; + childReportIDs.forEach((childReportID) => { + updatePolicyIdForReportAndThreads(childReportID, policyID, reportIDToThreadsReportIDsMap, optimisticData, failureData); + }); +} + +function buildOptimisticChangePolicyData(report: Report, policyID: string, optimisticPolicyExpenseChatReport?: Report | undefined) { + const optimisticData: OnyxUpdate[] = []; + const successData: OnyxUpdate[] = []; + const failureData: OnyxUpdate[] = []; + + // 1. Optimistically set the policyID on the report (and all its threads) by: + // 1.1 Preprocess reports to create a map of parentReportID to child reports list of reportIDs + // 1.2 Recursively update the policyID of the report and all its child reports + const reportID = report.reportID; + const reportIDToThreadsReportIDsMap = buildReportIDToThreadsReportIDsMap(); + updatePolicyIdForReportAndThreads(reportID, policyID, reportIDToThreadsReportIDsMap, optimisticData, failureData); + + // 2. If the old workspace had a expense chat, mark the report preview action as deleted + if (report.parentReportID && report.parentReportActionID) { + const oldWorkspaceChatReportID = report.parentReportID; + const oldReportPreviewActionID = report.parentReportActionID; + const oldReportPreviewAction = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oldWorkspaceChatReportID}`]?.[oldReportPreviewActionID]; + const deletedTime = DateUtils.getDBTime(); + const firstMessage = Array.isArray(oldReportPreviewAction?.message) ? oldReportPreviewAction.message.at(0) : null; + const updatedReportPreviewAction = { + ...oldReportPreviewAction, + originalMessage: { + deleted: deletedTime, + }, + ...(firstMessage && { + message: [ + { + ...firstMessage, + deleted: deletedTime, + }, + ...(Array.isArray(oldReportPreviewAction?.message) ? oldReportPreviewAction.message.slice(1) : []), + ], + }), + ...(!Array.isArray(oldReportPreviewAction?.message) && { + message: { + deleted: deletedTime, + }, + }), + }; + + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oldWorkspaceChatReportID}`, + value: {[oldReportPreviewActionID]: updatedReportPreviewAction}, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oldWorkspaceChatReportID}`, + value: {[oldReportPreviewActionID]: oldReportPreviewAction}, + }); + + // Update the expense chat report + const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${oldWorkspaceChatReportID}`]; + const lastMessageText = getLastVisibleMessage(oldWorkspaceChatReportID, {[oldReportPreviewActionID]: updatedReportPreviewAction as ReportAction})?.lastMessageText; + const lastVisibleActionCreated = getReportLastMessage(oldWorkspaceChatReportID, {[oldReportPreviewActionID]: updatedReportPreviewAction as ReportAction})?.lastVisibleActionCreated; + + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${oldWorkspaceChatReportID}`, + value: { + hasOutstandingChildRequest: false, + iouReportID: null, + lastMessageText, + lastVisibleActionCreated, + }, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${oldWorkspaceChatReportID}`, + value: chatReport, + }); + } + + // 3. Optimistically create a new REPORT_PREVIEW reportAction with the newReportPreviewActionID + // and set it as a parent of the moved report + const newPolicyExpenseChatReport = optimisticPolicyExpenseChatReport ?? getPolicyExpenseChat(currentUserAccountID, policyID); + const optimisticReportPreviewAction = buildOptimisticReportPreview(newPolicyExpenseChatReport, report); + + if (newPolicyExpenseChatReport) { + const newPolicyExpenseChatReportID = newPolicyExpenseChatReport.reportID; + + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newPolicyExpenseChatReportID}`, + value: {[optimisticReportPreviewAction.reportActionID]: optimisticReportPreviewAction}, + }); + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newPolicyExpenseChatReportID}`, + value: { + [optimisticReportPreviewAction.reportActionID]: { + pendingAction: null, + }, + }, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newPolicyExpenseChatReportID}`, + value: {[optimisticReportPreviewAction.reportActionID]: null}, + }); + + // Set the new report preview action as a parent of the moved report, + // and set the parentReportID on the moved report as the expense chat reportID + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: {parentReportActionID: optimisticReportPreviewAction.reportActionID, parentReportID: newPolicyExpenseChatReportID}, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: {parentReportActionID: report.parentReportActionID, parentReportID: report.parentReportID}, + }); + + // Set lastVisibleActionCreated + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${newPolicyExpenseChatReportID}`, + value: {lastVisibleActionCreated: optimisticReportPreviewAction?.created}, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${newPolicyExpenseChatReportID}`, + value: {lastVisibleActionCreated: newPolicyExpenseChatReport.lastVisibleActionCreated}, + }); + } + + // 4. Optimistically create a CHANGE_POLICY reportAction on the report using the reportActionID + const optimisticMovedReportAction = buildOptimisticChangePolicyReportAction(report.policyID, policyID); + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: {[optimisticMovedReportAction.reportActionID]: optimisticMovedReportAction}, + }); + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: { + [optimisticMovedReportAction.reportActionID]: { + pendingAction: null, + errors: null, + }, + }, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: { + [optimisticMovedReportAction.reportActionID]: { + errors: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), + }, + }, + }); + + return {optimisticData, successData, failureData, optimisticReportPreviewAction, optimisticMovedReportAction}; +} + /** * Check if a report has any forwarded actions */ @@ -10749,6 +10958,7 @@ export { buildOptimisticUnapprovedReportAction, buildOptimisticCancelPaymentReportAction, buildOptimisticChangedTaskAssigneeReportAction, + buildOptimisticChangePolicyData, buildOptimisticChatReport, buildOptimisticClosedReportAction, buildOptimisticCreatedReportAction, diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 559dbeaf3668..146e19602f12 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -77,6 +77,7 @@ import * as PhoneNumber from '@libs/PhoneNumber'; import * as PolicyUtils from '@libs/PolicyUtils'; import {goBackWhenEnableFeature, navigateToExpensifyCardPage} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import type {OptimisticChatReport} from '@libs/ReportUtils'; import type {PolicySelector} from '@pages/home/sidebar/FloatingActionButtonAndPopover'; import * as PaymentMethods from '@userActions/PaymentMethods'; import * as PersistedRequests from '@userActions/PersistedRequests'; @@ -111,6 +112,7 @@ type ReportCreationData = Record< { reportID: string; reportActionID?: string; + report: OptimisticChatReport; } >; @@ -1083,6 +1085,7 @@ function createPolicyExpenseChats(policyID: string, invitedEmailsToAccountIDs: I // If the chat already exists, we don't want to create a new one - just make sure it's not archived if (oldChat) { workspaceMembersChats.reportCreationData[login] = { + report: oldChat, reportID: oldChat.reportID, }; workspaceMembersChats.onyxOptimisticData.push({ @@ -1132,6 +1135,7 @@ function createPolicyExpenseChats(policyID: string, invitedEmailsToAccountIDs: I const optimisticCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(login); workspaceMembersChats.reportCreationData[login] = { + report: optimisticReport, reportID: optimisticReport.reportID, reportActionID: optimisticCreatedAction.reportActionID, }; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index e87120e7b251..60751cb8b136 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -89,7 +89,7 @@ import {shouldOnboardingRedirectToOldDot} from '@libs/OnboardingUtils'; import Parser from '@libs/Parser'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PhoneNumber from '@libs/PhoneNumber'; -import {getDefaultApprover, getPolicy, isPolicyAdmin as isPolicyAdminPolicyUtils, isPolicyMember} from '@libs/PolicyUtils'; +import {getDefaultApprover, getMemberAccountIDsForWorkspace, getPolicy, isPolicyAdmin as isPolicyAdminPolicyUtils, isPolicyMember} from '@libs/PolicyUtils'; import processReportIDDeeplink from '@libs/processReportIDDeeplink'; import Pusher from '@libs/Pusher'; import type {UserIsLeavingRoomEvent, UserIsTypingEvent} from '@libs/Pusher/types'; @@ -98,6 +98,7 @@ import type {OptimisticAddCommentReportAction, OptimisticChatReport, SelfDMParam import { buildOptimisticAddCommentReportAction, buildOptimisticChangeFieldAction, + buildOptimisticChangePolicyData, buildOptimisticChangePolicyReportAction, buildOptimisticChatReport, buildOptimisticCreatedReportAction, @@ -106,7 +107,6 @@ import { buildOptimisticGroupChatReport, buildOptimisticIOUReportAction, buildOptimisticRenamedRoomReportAction, - buildOptimisticReportPreview, buildOptimisticRoomDescriptionUpdatedReportAction, buildOptimisticSelfDMReport, buildOptimisticUnreportedTransactionAction, @@ -172,6 +172,7 @@ import type { PersonalDetailsList, Policy, PolicyEmployee, + PolicyEmployeeList, PolicyReportField, QuickAction, RecentlyUsedReportFields, @@ -191,7 +192,7 @@ import {clearByKey} from './CachedPDFPaths'; import {setDownload} from './Download'; import {close} from './Modal'; import navigateFromNotification from './navigateFromNotification'; -import {buildRoomMembersOnyxData} from './Policy/Member'; +import {buildAddMembersToWorkspaceOnyxData, buildRoomMembersOnyxData} from './Policy/Member'; import {createPolicyExpenseChats} from './Policy/Policy'; import { createUpdateCommentMatcher, @@ -5361,241 +5362,81 @@ function dismissChangePolicyModal() { } /** - * @private - * Builds a map of parentReportID to child report IDs for efficient traversal. + * Changes the policy of a report and all its child reports, and moves the report to the new policy's expense chat. */ -function buildReportIDToThreadsReportIDsMap(): Record { - const reportIDToThreadsReportIDsMap: Record = {}; - Object.values(allReports ?? {}).forEach((report) => { - if (!report?.parentReportID) { - return; - } - if (!reportIDToThreadsReportIDsMap[report.parentReportID]) { - reportIDToThreadsReportIDsMap[report.parentReportID] = []; - } - reportIDToThreadsReportIDsMap[report.parentReportID].push(report.reportID); - }); - return reportIDToThreadsReportIDsMap; -} +function changeReportPolicy(report: Report, policyID: string) { + if (!report || !policyID || report.policyID === policyID || !isExpenseReport(report)) { + return; + } -/** - * @private - * Recursively updates the policyID for a report and all its child reports. - */ -function updatePolicyIdForReportAndThreads( - currentReportID: string, - policyID: string, - reportIDToThreadsReportIDsMap: Record, - optimisticData: OnyxUpdate[], - failureData: OnyxUpdate[], -) { - const currentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${currentReportID}`]; - const originalPolicyID = currentReport?.policyID; + const {optimisticData, successData, failureData, optimisticReportPreviewAction, optimisticMovedReportAction} = buildOptimisticChangePolicyData(report, policyID); - if (originalPolicyID) { - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${currentReportID}`, - value: {policyID}, - }); - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${currentReportID}`, - value: {policyID: originalPolicyID}, - }); - } + const params = { + reportID: report.reportID, + policyID, + reportPreviewReportActionID: optimisticReportPreviewAction.reportActionID, + changePolicyReportActionID: optimisticMovedReportAction.reportActionID, + }; + API.write(WRITE_COMMANDS.CHANGE_REPORT_POLICY, params, {optimisticData, successData, failureData}); - // Recursively process child reports for the current report - const childReportIDs = reportIDToThreadsReportIDsMap[currentReportID] || []; - childReportIDs.forEach((childReportID) => { - updatePolicyIdForReportAndThreads(childReportID, policyID, reportIDToThreadsReportIDsMap, optimisticData, failureData); - }); + // 5. If the dismissedProductTraining.changeReportModal is not set, + // navigate to CHANGE_POLICY_EDUCATIONAL and a backTo param for the report page. + if (!nvpDismissedProductTraining?.[CONST.CHANGE_POLICY_TRAINING_MODAL]) { + Navigation.navigate(ROUTES.CHANGE_POLICY_EDUCATIONAL.getRoute(ROUTES.REPORT_WITH_ID.getRoute(report.reportID))); + } } /** * Changes the policy of a report and all its child reports, and moves the report to the new policy's expense chat. */ -function changeReportPolicy(reportID: string, policyID: string) { - if (!reportID || !policyID) { +function changeReportPolicyAndInviteSubmitter(report: Report, policyID: string, employeeList: PolicyEmployeeList | undefined) { + if (!report.reportID || !policyID || report.policyID === policyID || !isExpenseReport(report) || !report.ownerAccountID) { return; } - const reportToMove = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; - if (!reportToMove || reportToMove?.policyID === policyID || !isExpenseReport(reportToMove)) { - return; - } - - const optimisticData: OnyxUpdate[] = []; - const successData: OnyxUpdate[] = []; - const failureData: OnyxUpdate[] = []; - - // 1. Optimistically set the policyID on the report (and all its threads) - - // Preprocess reports to create a map of parentReportID to child reports list of reportIDs - const reportIDToThreadsReportIDsMap = buildReportIDToThreadsReportIDsMap(); - // Recursively update the policyID of the report and all its child reports - updatePolicyIdForReportAndThreads(reportID, policyID, reportIDToThreadsReportIDsMap, optimisticData, failureData); + const submitterEmail = PersonalDetailsUtils.getLoginByAccountID(report.ownerAccountID); - // 2. If the old workspace had a expense chat, mark the report preview action as deleted - if (reportToMove?.parentReportID && reportToMove?.parentReportActionID) { - const workspaceChatReportID = reportToMove.parentReportID; - const reportPreviewActionID = reportToMove.parentReportActionID; - const oldReportPreviewAction = allReportActions?.[workspaceChatReportID]?.[reportPreviewActionID]; - const deletedTime = DateUtils.getDBTime(); - const firstMessage = Array.isArray(oldReportPreviewAction?.message) ? oldReportPreviewAction.message.at(0) : null; - const updatedReportPreviewAction = { - ...oldReportPreviewAction, - originalMessage: { - deleted: deletedTime, - }, - ...(firstMessage && { - message: [ - { - ...firstMessage, - deleted: deletedTime, - }, - ...(Array.isArray(oldReportPreviewAction?.message) ? oldReportPreviewAction.message.slice(1) : []), - ], - }), - ...(!Array.isArray(oldReportPreviewAction?.message) && { - message: { - deleted: deletedTime, - }, - }), - }; - - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${workspaceChatReportID}`, - value: {[reportPreviewActionID]: updatedReportPreviewAction}, - }); - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${workspaceChatReportID}`, - value: { - [reportPreviewActionID]: { - ...oldReportPreviewAction, - originalMessage: { - deleted: null, - }, - }, - }, - }); - - // Update the expense chat report - const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${workspaceChatReportID}`]; - const lastMessageText = getLastVisibleMessage(workspaceChatReportID, {[reportPreviewActionID]: updatedReportPreviewAction as ReportAction})?.lastMessageText; - const lastVisibleActionCreated = getReportLastMessage(workspaceChatReportID, {[reportPreviewActionID]: updatedReportPreviewAction as ReportAction})?.lastVisibleActionCreated; - - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${workspaceChatReportID}`, - value: { - hasOutstandingChildRequest: false, - iouReportID: null, - lastMessageText, - lastVisibleActionCreated, - }, - }); - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${workspaceChatReportID}`, - value: chatReport, - }); + if (!submitterEmail) { + return; } + const policyMemberAccountIDs = Object.values(getMemberAccountIDsForWorkspace(employeeList, false, false)); + const {optimisticData, successData, failureData, membersChats} = buildAddMembersToWorkspaceOnyxData( + {[submitterEmail]: report.ownerAccountID}, + policyID, + policyMemberAccountIDs, + CONST.POLICY.ROLE.USER, + ); + const optimisticPolicyExpenseChatReport = membersChats.reportCreationData[submitterEmail].report; + const optimisticPolicyExpenseChatCreatedReportActionID = membersChats.reportCreationData[submitterEmail].reportActionID; - // 3. Optimistically create a new REPORT_PREVIEW reportAction with the newReportPreviewActionID - // and set it as a parent of the moved report - const policyExpenseChat = getPolicyExpenseChat(currentUserAccountID, policyID); - const optimisticReportPreviewAction = buildOptimisticReportPreview(policyExpenseChat, reportToMove); - - if (policyExpenseChat) { - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${policyExpenseChat.reportID}`, - value: {[optimisticReportPreviewAction.reportActionID]: optimisticReportPreviewAction}, - }); - successData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${policyExpenseChat.reportID}`, - value: { - [optimisticReportPreviewAction.reportActionID]: { - pendingAction: null, - }, - }, - }); - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${policyExpenseChat.reportID}`, - value: {[optimisticReportPreviewAction.reportActionID]: null}, - }); - - // Set the new report preview action as a parent of the moved report, - // and set the parentReportID on the moved report as the expense chat reportID - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: {parentReportActionID: optimisticReportPreviewAction.reportActionID, parentReportID: policyExpenseChat.reportID}, - }); - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: {parentReportActionID: reportToMove.parentReportActionID, parentReportID: reportToMove.parentReportID}, - }); - - // Set lastVisibleActionCreated - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${policyExpenseChat.reportID}`, - value: {lastVisibleActionCreated: optimisticReportPreviewAction?.created}, - }); - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${policyExpenseChat.reportID}`, - value: {lastVisibleActionCreated: policyExpenseChat.lastVisibleActionCreated}, - }); + if (!optimisticPolicyExpenseChatReport || !optimisticPolicyExpenseChatCreatedReportActionID) { + return; } - // 4. Optimistically create a CHANGE_POLICY reportAction on the report using the reportActionID - const optimisticMovedReportAction = buildOptimisticChangePolicyReportAction(reportToMove.policyID, policyID); - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportToMove.reportID}`, - value: {[optimisticMovedReportAction.reportActionID]: optimisticMovedReportAction}, - }); - successData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportToMove.reportID}`, - value: { - [optimisticMovedReportAction.reportActionID]: { - pendingAction: null, - errors: null, - }, - }, - }); - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportToMove.reportID}`, - value: { - [optimisticMovedReportAction.reportActionID]: { - errors: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), - }, - }, - }); + const { + optimisticData: optimisticChangePolicyData, + successData: successChangePolicyData, + failureData: failureChangePolicyData, + optimisticReportPreviewAction, + optimisticMovedReportAction, + } = buildOptimisticChangePolicyData(report, policyID, optimisticPolicyExpenseChatReport); + optimisticData.push(...optimisticChangePolicyData); + successData.push(...successChangePolicyData); + failureData.push(...failureChangePolicyData); - // Call the ChangeReportPolicy API endpoint const params = { - reportID: reportToMove.reportID, + reportID: report.reportID, policyID, reportPreviewReportActionID: optimisticReportPreviewAction.reportActionID, changePolicyReportActionID: optimisticMovedReportAction.reportActionID, + policyExpenseChatReportID: optimisticPolicyExpenseChatReport.reportID, + policyExpenseCreatedReportActionID: optimisticPolicyExpenseChatCreatedReportActionID, }; - API.write(WRITE_COMMANDS.CHANGE_REPORT_POLICY, params, {optimisticData, successData, failureData}); + API.write(WRITE_COMMANDS.CHANGE_REPORT_POLICY_AND_INVITE_SUBMITTER, params, {optimisticData, successData, failureData}); - // 5. If the dismissedProductTraining.changeReportModal is not set, - // navigate to CHANGE_POLICY_EDUCATIONAL and a backTo param for the report page. + // If the dismissedProductTraining.changeReportModal is not set, navigate to CHANGE_POLICY_EDUCATIONAL and a backTo param for the report page. if (!nvpDismissedProductTraining?.[CONST.CHANGE_POLICY_TRAINING_MODAL]) { - Navigation.navigate(ROUTES.CHANGE_POLICY_EDUCATIONAL.getRoute(ROUTES.REPORT_WITH_ID.getRoute(reportToMove.reportID))); + Navigation.navigate(ROUTES.CHANGE_POLICY_EDUCATIONAL.getRoute(ROUTES.REPORT_WITH_ID.getRoute(report.reportID))); } } @@ -5705,6 +5546,7 @@ export { moveIOUReportToPolicyAndInviteSubmitter, dismissChangePolicyModal, changeReportPolicy, + changeReportPolicyAndInviteSubmitter, removeFailedReport, openUnreportedExpense, }; diff --git a/src/pages/ReportChangeWorkspacePage.tsx b/src/pages/ReportChangeWorkspacePage.tsx index e4772fe3573a..ba035f90e550 100644 --- a/src/pages/ReportChangeWorkspacePage.tsx +++ b/src/pages/ReportChangeWorkspacePage.tsx @@ -11,13 +11,13 @@ import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import type {WorkspaceListItem} from '@hooks/useWorkspaceList'; import useWorkspaceList from '@hooks/useWorkspaceList'; -import {changeReportPolicy, moveIOUReportToPolicy, moveIOUReportToPolicyAndInviteSubmitter} from '@libs/actions/Report'; +import {changeReportPolicy, changeReportPolicyAndInviteSubmitter, moveIOUReportToPolicy, moveIOUReportToPolicyAndInviteSubmitter} from '@libs/actions/Report'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {ReportChangeWorkspaceNavigatorParamList} from '@libs/Navigation/types'; import {getLoginByAccountID} from '@libs/PersonalDetailsUtils'; import {getPolicy, isPolicyAdmin, isPolicyMember} from '@libs/PolicyUtils'; -import {isIOUReport, isMoneyRequestReport, isMoneyRequestReportPendingDeletion, isWorkspaceEligibleForReportChange} from '@libs/ReportUtils'; +import {isExpenseReport, isIOUReport, isMoneyRequestReport, isMoneyRequestReportPendingDeletion, isWorkspaceEligibleForReportChange} from '@libs/ReportUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; @@ -51,11 +51,14 @@ function ReportChangeWorkspacePage({report}: ReportChangeWorkspacePageProps) { moveIOUReportToPolicyAndInviteSubmitter(reportID, policyID); } else if (isIOUReport(reportID) && isPolicyMember(session?.email, policyID)) { moveIOUReportToPolicy(reportID, policyID); + } else if (isExpenseReport(report) && isPolicyAdmin(getPolicy(policyID)) && report.ownerAccountID && !isPolicyMember(getLoginByAccountID(report.ownerAccountID), policyID)) { + const employeeList = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.employeeList; + changeReportPolicyAndInviteSubmitter(report, policyID, employeeList); } else { - changeReportPolicy(reportID, policyID); + changeReportPolicy(report, policyID); } }, - [session?.email, report, reportID], + [session?.email, report, reportID, policies], ); const {sections, shouldShowNoResultsFoundMessage, shouldShowSearchInput} = useWorkspaceList({ From 62073099047f0283f605f15fd3a8d6b834bdba2d Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 10 Jun 2025 14:35:26 -0600 Subject: [PATCH 2/8] fix bug --- src/libs/ReportUtils.ts | 12 ++++++------ src/libs/actions/Policy/Policy.ts | 4 ---- src/libs/actions/Report.ts | 8 ++++---- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 4246400030f1..84ca5dc90abf 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -9675,7 +9675,7 @@ function updatePolicyIdForReportAndThreads( }); } -function buildOptimisticChangePolicyData(report: Report, policyID: string, optimisticPolicyExpenseChatReport?: Report | undefined) { +function buildOptimisticChangePolicyData(report: Report, policyID: string) { const optimisticData: OnyxUpdate[] = []; const successData: OnyxUpdate[] = []; const failureData: OnyxUpdate[] = []; @@ -9750,11 +9750,11 @@ function buildOptimisticChangePolicyData(report: Report, policyID: string, optim // 3. Optimistically create a new REPORT_PREVIEW reportAction with the newReportPreviewActionID // and set it as a parent of the moved report - const newPolicyExpenseChatReport = optimisticPolicyExpenseChatReport ?? getPolicyExpenseChat(currentUserAccountID, policyID); - const optimisticReportPreviewAction = buildOptimisticReportPreview(newPolicyExpenseChatReport, report); + const policyExpenseChat = getPolicyExpenseChat(currentUserAccountID, policyID); + const optimisticReportPreviewAction = buildOptimisticReportPreview(policyExpenseChat, report); - if (newPolicyExpenseChatReport) { - const newPolicyExpenseChatReportID = newPolicyExpenseChatReport.reportID; + if (policyExpenseChat) { + const newPolicyExpenseChatReportID = policyExpenseChat.reportID; optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -9798,7 +9798,7 @@ function buildOptimisticChangePolicyData(report: Report, policyID: string, optim failureData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${newPolicyExpenseChatReportID}`, - value: {lastVisibleActionCreated: newPolicyExpenseChatReport.lastVisibleActionCreated}, + value: {lastVisibleActionCreated: policyExpenseChat.lastVisibleActionCreated}, }); } diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 146e19602f12..559dbeaf3668 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -77,7 +77,6 @@ import * as PhoneNumber from '@libs/PhoneNumber'; import * as PolicyUtils from '@libs/PolicyUtils'; import {goBackWhenEnableFeature, navigateToExpensifyCardPage} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import type {OptimisticChatReport} from '@libs/ReportUtils'; import type {PolicySelector} from '@pages/home/sidebar/FloatingActionButtonAndPopover'; import * as PaymentMethods from '@userActions/PaymentMethods'; import * as PersistedRequests from '@userActions/PersistedRequests'; @@ -112,7 +111,6 @@ type ReportCreationData = Record< { reportID: string; reportActionID?: string; - report: OptimisticChatReport; } >; @@ -1085,7 +1083,6 @@ function createPolicyExpenseChats(policyID: string, invitedEmailsToAccountIDs: I // If the chat already exists, we don't want to create a new one - just make sure it's not archived if (oldChat) { workspaceMembersChats.reportCreationData[login] = { - report: oldChat, reportID: oldChat.reportID, }; workspaceMembersChats.onyxOptimisticData.push({ @@ -1135,7 +1132,6 @@ function createPolicyExpenseChats(policyID: string, invitedEmailsToAccountIDs: I const optimisticCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(login); workspaceMembersChats.reportCreationData[login] = { - report: optimisticReport, reportID: optimisticReport.reportID, reportActionID: optimisticCreatedAction.reportActionID, }; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 60751cb8b136..9f7a33ec5757 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -5406,10 +5406,10 @@ function changeReportPolicyAndInviteSubmitter(report: Report, policyID: string, policyMemberAccountIDs, CONST.POLICY.ROLE.USER, ); - const optimisticPolicyExpenseChatReport = membersChats.reportCreationData[submitterEmail].report; + const optimisticPolicyExpenseChatReportID = membersChats.reportCreationData[submitterEmail].reportID; const optimisticPolicyExpenseChatCreatedReportActionID = membersChats.reportCreationData[submitterEmail].reportActionID; - if (!optimisticPolicyExpenseChatReport || !optimisticPolicyExpenseChatCreatedReportActionID) { + if (!optimisticPolicyExpenseChatReportID || !optimisticPolicyExpenseChatCreatedReportActionID) { return; } @@ -5419,7 +5419,7 @@ function changeReportPolicyAndInviteSubmitter(report: Report, policyID: string, failureData: failureChangePolicyData, optimisticReportPreviewAction, optimisticMovedReportAction, - } = buildOptimisticChangePolicyData(report, policyID, optimisticPolicyExpenseChatReport); + } = buildOptimisticChangePolicyData(report, policyID); optimisticData.push(...optimisticChangePolicyData); successData.push(...successChangePolicyData); failureData.push(...failureChangePolicyData); @@ -5429,7 +5429,7 @@ function changeReportPolicyAndInviteSubmitter(report: Report, policyID: string, policyID, reportPreviewReportActionID: optimisticReportPreviewAction.reportActionID, changePolicyReportActionID: optimisticMovedReportAction.reportActionID, - policyExpenseChatReportID: optimisticPolicyExpenseChatReport.reportID, + policyExpenseChatReportID: optimisticPolicyExpenseChatReportID, policyExpenseCreatedReportActionID: optimisticPolicyExpenseChatCreatedReportActionID, }; API.write(WRITE_COMMANDS.CHANGE_REPORT_POLICY_AND_INVITE_SUBMITTER, params, {optimisticData, successData, failureData}); From e84a28996853007c90ad8f099543e728c2be6e85 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 10 Jun 2025 14:49:55 -0600 Subject: [PATCH 3/8] fix eslint --- src/pages/ReportChangeWorkspacePage.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/ReportChangeWorkspacePage.tsx b/src/pages/ReportChangeWorkspacePage.tsx index ba035f90e550..d5da74023b4c 100644 --- a/src/pages/ReportChangeWorkspacePage.tsx +++ b/src/pages/ReportChangeWorkspacePage.tsx @@ -51,6 +51,8 @@ function ReportChangeWorkspacePage({report}: ReportChangeWorkspacePageProps) { moveIOUReportToPolicyAndInviteSubmitter(reportID, policyID); } else if (isIOUReport(reportID) && isPolicyMember(session?.email, policyID)) { moveIOUReportToPolicy(reportID, policyID); + // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850 + // eslint-disable-next-line deprecation/deprecation } else if (isExpenseReport(report) && isPolicyAdmin(getPolicy(policyID)) && report.ownerAccountID && !isPolicyMember(getLoginByAccountID(report.ownerAccountID), policyID)) { const employeeList = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.employeeList; changeReportPolicyAndInviteSubmitter(report, policyID, employeeList); From b523a85485218eab66302a4cbc6691fe0435fd23 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 10 Jun 2025 14:54:40 -0600 Subject: [PATCH 4/8] fix prettier --- src/pages/ReportChangeWorkspacePage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/ReportChangeWorkspacePage.tsx b/src/pages/ReportChangeWorkspacePage.tsx index d5da74023b4c..58a714e0d021 100644 --- a/src/pages/ReportChangeWorkspacePage.tsx +++ b/src/pages/ReportChangeWorkspacePage.tsx @@ -51,8 +51,8 @@ function ReportChangeWorkspacePage({report}: ReportChangeWorkspacePageProps) { moveIOUReportToPolicyAndInviteSubmitter(reportID, policyID); } else if (isIOUReport(reportID) && isPolicyMember(session?.email, policyID)) { moveIOUReportToPolicy(reportID, policyID); - // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850 - // eslint-disable-next-line deprecation/deprecation + // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850 + // eslint-disable-next-line deprecation/deprecation } else if (isExpenseReport(report) && isPolicyAdmin(getPolicy(policyID)) && report.ownerAccountID && !isPolicyMember(getLoginByAccountID(report.ownerAccountID), policyID)) { const employeeList = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.employeeList; changeReportPolicyAndInviteSubmitter(report, policyID, employeeList); From 4c2230012cec3499c37fd6252148fd23b7cc28fb Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Fri, 13 Jun 2025 13:25:20 -0600 Subject: [PATCH 5/8] address comments --- src/libs/ReportUtils.ts | 94 +++++++++++++++++++------------------- src/libs/actions/Report.ts | 22 +++++---- 2 files changed, 59 insertions(+), 57 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 5b42efe1a01e..6011df0f329e 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -9691,7 +9691,7 @@ function buildReportIDToThreadsReportIDsMap(): Record { * @private * Recursively updates the policyID for a report and all its child reports. */ -function updatePolicyIdForReportAndThreads( +function updatePolicyIDForReportAndThreads( currentReportID: string, policyID: string, reportIDToThreadsReportIDsMap: Record, @@ -9717,7 +9717,7 @@ function updatePolicyIdForReportAndThreads( // Recursively process child reports for the current report const childReportIDs = reportIDToThreadsReportIDsMap[currentReportID] || []; childReportIDs.forEach((childReportID) => { - updatePolicyIdForReportAndThreads(childReportID, policyID, reportIDToThreadsReportIDsMap, optimisticData, failureData); + updatePolicyIDForReportAndThreads(childReportID, policyID, reportIDToThreadsReportIDsMap, optimisticData, failureData); }); } @@ -9731,9 +9731,9 @@ function buildOptimisticChangePolicyData(report: Report, policyID: string) { // 1.2 Recursively update the policyID of the report and all its child reports const reportID = report.reportID; const reportIDToThreadsReportIDsMap = buildReportIDToThreadsReportIDsMap(); - updatePolicyIdForReportAndThreads(reportID, policyID, reportIDToThreadsReportIDsMap, optimisticData, failureData); + updatePolicyIDForReportAndThreads(reportID, policyID, reportIDToThreadsReportIDsMap, optimisticData, failureData); - // 2. If the old workspace had a expense chat, mark the report preview action as deleted + // 2. If this is a thread, we have to mark the parent report preview action as deleted to properly update the UI if (report.parentReportID && report.parentReportActionID) { const oldWorkspaceChatReportID = report.parentReportID; const oldReportPreviewActionID = report.parentReportActionID; @@ -9799,54 +9799,52 @@ function buildOptimisticChangePolicyData(report: Report, policyID: string) { const policyExpenseChat = getPolicyExpenseChat(currentUserAccountID, policyID); const optimisticReportPreviewAction = buildOptimisticReportPreview(policyExpenseChat, report); - if (policyExpenseChat) { - const newPolicyExpenseChatReportID = policyExpenseChat.reportID; + const newPolicyExpenseChatReportID = policyExpenseChat?.reportID; - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newPolicyExpenseChatReportID}`, - value: {[optimisticReportPreviewAction.reportActionID]: optimisticReportPreviewAction}, - }); - successData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newPolicyExpenseChatReportID}`, - value: { - [optimisticReportPreviewAction.reportActionID]: { - pendingAction: null, - }, + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newPolicyExpenseChatReportID}`, + value: {[optimisticReportPreviewAction.reportActionID]: optimisticReportPreviewAction}, + }); + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newPolicyExpenseChatReportID}`, + value: { + [optimisticReportPreviewAction.reportActionID]: { + pendingAction: null, }, - }); - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newPolicyExpenseChatReportID}`, - value: {[optimisticReportPreviewAction.reportActionID]: null}, - }); + }, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newPolicyExpenseChatReportID}`, + value: {[optimisticReportPreviewAction.reportActionID]: null}, + }); - // Set the new report preview action as a parent of the moved report, - // and set the parentReportID on the moved report as the expense chat reportID - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: {parentReportActionID: optimisticReportPreviewAction.reportActionID, parentReportID: newPolicyExpenseChatReportID}, - }); - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: {parentReportActionID: report.parentReportActionID, parentReportID: report.parentReportID}, - }); + // Set the new report preview action as a parent of the moved report, + // and set the parentReportID on the moved report as the expense chat reportID + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: {parentReportActionID: optimisticReportPreviewAction.reportActionID, parentReportID: newPolicyExpenseChatReportID}, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: {parentReportActionID: report.parentReportActionID, parentReportID: report.parentReportID}, + }); - // Set lastVisibleActionCreated - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${newPolicyExpenseChatReportID}`, - value: {lastVisibleActionCreated: optimisticReportPreviewAction?.created}, - }); - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${newPolicyExpenseChatReportID}`, - value: {lastVisibleActionCreated: policyExpenseChat.lastVisibleActionCreated}, - }); - } + // Set lastVisibleActionCreated + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${newPolicyExpenseChatReportID}`, + value: {lastVisibleActionCreated: optimisticReportPreviewAction?.created}, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${newPolicyExpenseChatReportID}`, + value: {lastVisibleActionCreated: policyExpenseChat?.lastVisibleActionCreated}, + }); // 4. Optimistically create a CHANGE_POLICY reportAction on the report using the reportActionID const optimisticMovedReportAction = buildOptimisticChangePolicyReportAction(report.policyID, policyID); diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index dff1c27ce72f..499f5cdd8b3f 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -5370,6 +5370,13 @@ function dismissChangePolicyModal() { API.write(WRITE_COMMANDS.DISMISS_PRODUCT_TRAINING, {name: CONST.CHANGE_POLICY_TRAINING_MODAL, dismissedMethod: 'click'}, {optimisticData}); } +function navigateToTrainingModal(dismissedProductTrainingNVP: OnyxEntry, reportID: string) { + if (dismissedProductTrainingNVP?.[CONST.CHANGE_POLICY_TRAINING_MODAL]) { + return; + } + Navigation.navigate(ROUTES.CHANGE_POLICY_EDUCATIONAL.getRoute(ROUTES.REPORT_WITH_ID.getRoute(reportID))); +} + /** * Changes the policy of a report and all its child reports, and moves the report to the new policy's expense chat. */ @@ -5388,15 +5395,13 @@ function changeReportPolicy(report: Report, policyID: string) { }; API.write(WRITE_COMMANDS.CHANGE_REPORT_POLICY, params, {optimisticData, successData, failureData}); - // 5. If the dismissedProductTraining.changeReportModal is not set, + // If the dismissedProductTraining.changeReportModal is not set, // navigate to CHANGE_POLICY_EDUCATIONAL and a backTo param for the report page. - if (!nvpDismissedProductTraining?.[CONST.CHANGE_POLICY_TRAINING_MODAL]) { - Navigation.navigate(ROUTES.CHANGE_POLICY_EDUCATIONAL.getRoute(ROUTES.REPORT_WITH_ID.getRoute(report.reportID))); - } + navigateToTrainingModal(nvpDismissedProductTraining, report.reportID); } /** - * Changes the policy of a report and all its child reports, and moves the report to the new policy's expense chat. + * Invites the submitter to the new report policy, changes the policy of a report and all its child reports, and moves the report to the new policy's expense chat */ function changeReportPolicyAndInviteSubmitter(report: Report, policyID: string, employeeList: PolicyEmployeeList | undefined) { if (!report.reportID || !policyID || report.policyID === policyID || !isExpenseReport(report) || !report.ownerAccountID) { @@ -5443,10 +5448,9 @@ function changeReportPolicyAndInviteSubmitter(report: Report, policyID: string, }; API.write(WRITE_COMMANDS.CHANGE_REPORT_POLICY_AND_INVITE_SUBMITTER, params, {optimisticData, successData, failureData}); - // If the dismissedProductTraining.changeReportModal is not set, navigate to CHANGE_POLICY_EDUCATIONAL and a backTo param for the report page. - if (!nvpDismissedProductTraining?.[CONST.CHANGE_POLICY_TRAINING_MODAL]) { - Navigation.navigate(ROUTES.CHANGE_POLICY_EDUCATIONAL.getRoute(ROUTES.REPORT_WITH_ID.getRoute(report.reportID))); - } + // If the dismissedProductTraining.changeReportModal is not set, + // navigate to CHANGE_POLICY_EDUCATIONAL and a backTo param for the report page. + navigateToTrainingModal(nvpDismissedProductTraining, report.reportID); } export type {Video, GuidedSetupData, TaskForParameters, IntroSelected}; From 37d2ef803b9091ecc240242606454362ea77b4ca Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 16 Jun 2025 13:29:18 -0600 Subject: [PATCH 6/8] fix import --- src/pages/ReportChangeWorkspacePage.tsx | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/pages/ReportChangeWorkspacePage.tsx b/src/pages/ReportChangeWorkspacePage.tsx index 4f611a303c19..a0f0071ba313 100644 --- a/src/pages/ReportChangeWorkspacePage.tsx +++ b/src/pages/ReportChangeWorkspacePage.tsx @@ -58,17 +58,10 @@ function ReportChangeWorkspacePage({report}: ReportChangeWorkspacePageProps) { const employeeList = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.employeeList; changeReportPolicyAndInviteSubmitter(report, policyID, employeeList); } else { -<<<<<<< HEAD - changeReportPolicy(report, policyID); + changeReportPolicy(report, policyID, reportNextStep); } }, - [session?.email, report, reportID, policies], -======= - changeReportPolicy(reportID, policyID, reportNextStep); - } - }, - [session?.email, report, reportID, reportNextStep], ->>>>>>> main + [session?.email, report, reportID, reportNextStep, policies], ); const {sections, shouldShowNoResultsFoundMessage, shouldShowSearchInput} = useWorkspaceList({ From 1b90a254c238f382189aa53dbdb01ba13c40582d Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 16 Jun 2025 13:33:00 -0600 Subject: [PATCH 7/8] move functions --- src/libs/ReportUtils.ts | 54 ------------------------------------ src/libs/actions/Report.ts | 56 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 57 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index aaee19eec8bc..70899baff4d3 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -9710,58 +9710,6 @@ function createDraftTransactionAndNavigateToParticipantSelector( return createDraftWorkspaceAndNavigateToConfirmationScreen(transactionID, actionName); } -/** - * @private - * Builds a map of parentReportID to child report IDs for efficient traversal. - */ -function buildReportIDToThreadsReportIDsMap(): Record { - const reportIDToThreadsReportIDsMap: Record = {}; - Object.values(allReports ?? {}).forEach((report) => { - if (!report?.parentReportID) { - return; - } - if (!reportIDToThreadsReportIDsMap[report.parentReportID]) { - reportIDToThreadsReportIDsMap[report.parentReportID] = []; - } - reportIDToThreadsReportIDsMap[report.parentReportID].push(report.reportID); - }); - return reportIDToThreadsReportIDsMap; -} - -/** - * @private - * Recursively updates the policyID for a report and all its child reports. - */ -function updatePolicyIDForReportAndThreads( - currentReportID: string, - policyID: string, - reportIDToThreadsReportIDsMap: Record, - optimisticData: OnyxUpdate[], - failureData: OnyxUpdate[], -) { - const currentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${currentReportID}`]; - const originalPolicyID = currentReport?.policyID; - - if (originalPolicyID) { - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${currentReportID}`, - value: {policyID}, - }); - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${currentReportID}`, - value: {policyID: originalPolicyID}, - }); - } - - // Recursively process child reports for the current report - const childReportIDs = reportIDToThreadsReportIDsMap[currentReportID] || []; - childReportIDs.forEach((childReportID) => { - updatePolicyIDForReportAndThreads(childReportID, policyID, reportIDToThreadsReportIDsMap, optimisticData, failureData); - }); -} - /** * Check if a report has any forwarded actions */ @@ -10901,7 +10849,6 @@ export { addDomainToShortMention, completeShortMention, areAllRequestsBeingSmartScanned, - buildReportIDToThreadsReportIDsMap, buildOptimisticAddCommentReportAction, buildOptimisticApprovedReportAction, buildOptimisticUnapprovedReportAction, @@ -11273,7 +11220,6 @@ export { hasReportBeenReopened, getMoneyReportPreviewName, getNextApproverAccountID, - updatePolicyIDForReportAndThreads, }; export type { diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index ff64bf7e27e0..49b1a344692b 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -96,7 +96,6 @@ import type {UserIsLeavingRoomEvent, UserIsTypingEvent} from '@libs/Pusher/types import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import type {OptimisticAddCommentReportAction, OptimisticChatReport, SelfDMParameters} from '@libs/ReportUtils'; import { - buildReportIDToThreadsReportIDsMap, buildOptimisticAddCommentReportAction, buildOptimisticChangeFieldAction, buildOptimisticChangePolicyReportAction, @@ -152,7 +151,6 @@ import { isUnread, isValidReportIDFromPath, prepareOnboardingOnyxData, - updatePolicyIDForReportAndThreads, } from '@libs/ReportUtils'; import shouldSkipDeepLinkNavigation from '@libs/shouldSkipDeepLinkNavigation'; import playSound, {SOUNDS} from '@libs/Sound'; @@ -5385,6 +5383,58 @@ function dismissChangePolicyModal() { API.write(WRITE_COMMANDS.DISMISS_PRODUCT_TRAINING, {name: CONST.CHANGE_POLICY_TRAINING_MODAL, dismissedMethod: 'click'}, {optimisticData}); } +/** + * @private + * Builds a map of parentReportID to child report IDs for efficient traversal. + */ +function buildReportIDToThreadsReportIDsMap(): Record { + const reportIDToThreadsReportIDsMap: Record = {}; + Object.values(allReports ?? {}).forEach((report) => { + if (!report?.parentReportID) { + return; + } + if (!reportIDToThreadsReportIDsMap[report.parentReportID]) { + reportIDToThreadsReportIDsMap[report.parentReportID] = []; + } + reportIDToThreadsReportIDsMap[report.parentReportID].push(report.reportID); + }); + return reportIDToThreadsReportIDsMap; +} + +/** + * @private + * Recursively updates the policyID for a report and all its child reports. + */ +function updatePolicyIdForReportAndThreads( + currentReportID: string, + policyID: string, + reportIDToThreadsReportIDsMap: Record, + optimisticData: OnyxUpdate[], + failureData: OnyxUpdate[], +) { + const currentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${currentReportID}`]; + const originalPolicyID = currentReport?.policyID; + + if (originalPolicyID) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${currentReportID}`, + value: {policyID}, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${currentReportID}`, + value: {policyID: originalPolicyID}, + }); + } + + // Recursively process child reports for the current report + const childReportIDs = reportIDToThreadsReportIDsMap[currentReportID] || []; + childReportIDs.forEach((childReportID) => { + updatePolicyIdForReportAndThreads(childReportID, policyID, reportIDToThreadsReportIDsMap, optimisticData, failureData); + }); +} + function navigateToTrainingModal(dismissedProductTrainingNVP: OnyxEntry, reportID: string) { if (dismissedProductTrainingNVP?.[CONST.CHANGE_POLICY_TRAINING_MODAL]) { return; @@ -5402,7 +5452,7 @@ function buildOptimisticChangePolicyData(report: Report, policyID: string, repor // 1.2 Recursively update the policyID of the report and all its child reports const reportID = report.reportID; const reportIDToThreadsReportIDsMap = buildReportIDToThreadsReportIDsMap(); - updatePolicyIDForReportAndThreads(reportID, policyID, reportIDToThreadsReportIDsMap, optimisticData, failureData); + updatePolicyIdForReportAndThreads(reportID, policyID, reportIDToThreadsReportIDsMap, optimisticData, failureData); // We reopen and reassign the report if the report is open/submitted and the manager is not a member of the new policy. This is to prevent the old manager from seeing a report that they can't action on. const isOpenOrSubmitted = isOpenExpenseReport(report) || isProcessingReport(report); From 714c58f01795b610b7a4c973e3a5a6e8fda03c30 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Mon, 16 Jun 2025 13:34:26 -0600 Subject: [PATCH 8/8] rm spaces --- src/libs/actions/Report.ts | 91 +++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 46 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 49b1a344692b..142b6d8fb9bc 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -5385,54 +5385,54 @@ function dismissChangePolicyModal() { /** * @private - * Builds a map of parentReportID to child report IDs for efficient traversal. + * Builds a map of parentReportID to child report IDs for efficient traversal. */ -function buildReportIDToThreadsReportIDsMap(): Record { - const reportIDToThreadsReportIDsMap: Record = {}; - Object.values(allReports ?? {}).forEach((report) => { - if (!report?.parentReportID) { - return; - } - if (!reportIDToThreadsReportIDsMap[report.parentReportID]) { - reportIDToThreadsReportIDsMap[report.parentReportID] = []; - } - reportIDToThreadsReportIDsMap[report.parentReportID].push(report.reportID); - }); - return reportIDToThreadsReportIDsMap; +function buildReportIDToThreadsReportIDsMap(): Record { + const reportIDToThreadsReportIDsMap: Record = {}; + Object.values(allReports ?? {}).forEach((report) => { + if (!report?.parentReportID) { + return; + } + if (!reportIDToThreadsReportIDsMap[report.parentReportID]) { + reportIDToThreadsReportIDsMap[report.parentReportID] = []; + } + reportIDToThreadsReportIDsMap[report.parentReportID].push(report.reportID); + }); + return reportIDToThreadsReportIDsMap; } -/** - * @private - * Recursively updates the policyID for a report and all its child reports. - */ -function updatePolicyIdForReportAndThreads( - currentReportID: string, - policyID: string, - reportIDToThreadsReportIDsMap: Record, - optimisticData: OnyxUpdate[], - failureData: OnyxUpdate[], -) { - const currentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${currentReportID}`]; - const originalPolicyID = currentReport?.policyID; - - if (originalPolicyID) { - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${currentReportID}`, - value: {policyID}, - }); - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${currentReportID}`, - value: {policyID: originalPolicyID}, - }); - } - - // Recursively process child reports for the current report - const childReportIDs = reportIDToThreadsReportIDsMap[currentReportID] || []; - childReportIDs.forEach((childReportID) => { - updatePolicyIdForReportAndThreads(childReportID, policyID, reportIDToThreadsReportIDsMap, optimisticData, failureData); - }); +/** + * @private + * Recursively updates the policyID for a report and all its child reports. + */ +function updatePolicyIdForReportAndThreads( + currentReportID: string, + policyID: string, + reportIDToThreadsReportIDsMap: Record, + optimisticData: OnyxUpdate[], + failureData: OnyxUpdate[], +) { + const currentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${currentReportID}`]; + const originalPolicyID = currentReport?.policyID; + + if (originalPolicyID) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${currentReportID}`, + value: {policyID}, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${currentReportID}`, + value: {policyID: originalPolicyID}, + }); + } + + // Recursively process child reports for the current report + const childReportIDs = reportIDToThreadsReportIDsMap[currentReportID] || []; + childReportIDs.forEach((childReportID) => { + updatePolicyIdForReportAndThreads(childReportID, policyID, reportIDToThreadsReportIDsMap, optimisticData, failureData); + }); } function navigateToTrainingModal(dismissedProductTrainingNVP: OnyxEntry, reportID: string) { @@ -5636,7 +5636,6 @@ function buildOptimisticChangePolicyData(report: Report, policyID: string, repor return {optimisticData, successData, failureData, optimisticReportPreviewAction, optimisticMovedReportAction}; } - /** * Changes the policy of a report and all its child reports, and moves the report to the new policy's expense chat. */