diff --git a/src/libs/API/parameters/ApproveMoneyRequestParams.ts b/src/libs/API/parameters/ApproveMoneyRequestParams.ts index 521226aeeff24..d67bc97b7de3d 100644 --- a/src/libs/API/parameters/ApproveMoneyRequestParams.ts +++ b/src/libs/API/parameters/ApproveMoneyRequestParams.ts @@ -12,6 +12,14 @@ type ApproveMoneyRequestParams = { * }> */ optimisticHoldReportExpenseActionIDs?: string; + /** + * Stringified JSON object with type of following structure: + * Array<{ + * optimisticReportActionID: string; + * oldReportActionID: string; + * }> + */ + optimisticDuplicatedReportActionIDs?: string; }; export default ApproveMoneyRequestParams; diff --git a/src/libs/API/parameters/PayMoneyRequestParams.ts b/src/libs/API/parameters/PayMoneyRequestParams.ts index 337c38d7164ea..af93975566087 100644 --- a/src/libs/API/parameters/PayMoneyRequestParams.ts +++ b/src/libs/API/parameters/PayMoneyRequestParams.ts @@ -17,6 +17,14 @@ type PayMoneyRequestParams = { * }> */ optimisticHoldReportExpenseActionIDs?: string; + /** + * Stringified JSON object with type of following structure: + * Array<{ + * optimisticReportActionID: string; + * oldReportActionID: string; + * }> + */ + optimisticDuplicatedReportActionIDs?: string; }; export default PayMoneyRequestParams; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index bef8fc4e6f69b..81a4aa5719a62 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -10164,6 +10164,93 @@ function getHoldReportActionsAndTransactions(reportID: string | undefined) { return {holdReportActions, holdTransactions}; } +/** + * Gets duplicate workflow actions for a partial expense report. + * Used when splitting held expenses into a new partial report to maintain action history. + * + * @param sourceReportID - The ID of the original report to copy actions from + * @param targetReportID - The ID of the new partial expense report to copy actions to + * @returns A tuple of [optimisticData, successData, failureData, duplicatedReportActionIDs] + */ +function getDuplicateActionsForPartialReport( + sourceReportID: string | undefined, + targetReportID: string | undefined, +): [ + Array>, + Array>, + Array>, + OptimisticHoldReportExpenseActionID[], +] { + const optimisticData: Array> = []; + const successData: Array> = []; + const failureData: Array> = []; + const optimisticDuplicatedReportActionIDs: OptimisticHoldReportExpenseActionID[] = []; + + if (!sourceReportID || !targetReportID) { + return [optimisticData, successData, failureData, optimisticDuplicatedReportActionIDs]; + } + + const sourceReportActions = getAllReportActions(sourceReportID); + + // Match the backend's WORKFLOW_ACTIONS list + const workflowActionTypes = [ + CONST.REPORT.ACTIONS.TYPE.SUBMITTED, + CONST.REPORT.ACTIONS.TYPE.SUBMITTED_AND_CLOSED, + CONST.REPORT.ACTIONS.TYPE.APPROVED, + CONST.REPORT.ACTIONS.TYPE.UNAPPROVED, + CONST.REPORT.ACTIONS.TYPE.REJECTED, + CONST.REPORT.ACTIONS.TYPE.RETRACTED, + CONST.REPORT.ACTIONS.TYPE.CLOSED, + CONST.REPORT.ACTIONS.TYPE.REOPENED, + CONST.REPORT.ACTIONS.TYPE.FORWARDED, + CONST.REPORT.ACTIONS.TYPE.TAKE_CONTROL, + CONST.REPORT.ACTIONS.TYPE.REROUTE, + ] as const; + + const copiedActions: Record = {}; + const copiedActionsSuccess: OnyxCollection> = {}; + const copiedActionsFailure: Record = {}; + + for (const action of Object.values(sourceReportActions)) { + if (action && (workflowActionTypes as readonly string[]).includes(action.actionName)) { + const newActionID = NumberUtils.rand64(); + copiedActions[newActionID] = { + ...action, + reportActionID: newActionID, + reportID: targetReportID, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }; + copiedActionsSuccess[newActionID] = { + pendingAction: null, + }; + copiedActionsFailure[newActionID] = null; + optimisticDuplicatedReportActionIDs.push({optimisticReportActionID: newActionID, oldReportActionID: action.reportActionID}); + } + } + + if (Object.keys(copiedActions).length > 0) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetReportID}`, + value: copiedActions, + }); + + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetReportID}`, + value: copiedActionsSuccess, + }); + + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetReportID}`, + value: copiedActionsFailure, + }); + } + + return [optimisticData, successData, failureData, optimisticDuplicatedReportActionIDs]; +} + function getReportFromHoldRequestsOnyxData( chatReport: OnyxTypes.Report, iouReport: OnyxEntry, @@ -10172,6 +10259,7 @@ function getReportFromHoldRequestsOnyxData( optimisticHoldReportID: string; optimisticHoldActionID: string; optimisticHoldReportExpenseActionIDs: OptimisticHoldReportExpenseActionID[]; + optimisticDuplicatedReportActionIDs: OptimisticHoldReportExpenseActionID[]; optimisticData: OnyxUpdate[]; successData: OnyxUpdate[]; failureData: OnyxUpdate[]; @@ -10389,6 +10477,15 @@ function getReportFromHoldRequestsOnyxData( }, ]; + // Copy submission/approval actions to the new report + const [copiedActionsOptimistic, copiedActionsSuccess, copiedActionsFailure, optimisticDuplicatedReportActionIDs] = getDuplicateActionsForPartialReport( + iouReport?.reportID, + optimisticExpenseReport.reportID, + ); + optimisticData.push(...copiedActionsOptimistic); + successData.push(...copiedActionsSuccess); + failureData.push(...copiedActionsFailure); + return { optimisticData, optimisticHoldActionID: optimisticExpenseReportPreview.reportActionID, @@ -10396,6 +10493,7 @@ function getReportFromHoldRequestsOnyxData( successData, optimisticHoldReportID: optimisticExpenseReport.reportID, optimisticHoldReportExpenseActionIDs, + optimisticDuplicatedReportActionIDs, }; } @@ -10715,6 +10813,7 @@ function getPayMoneyRequestParams({ let optimisticHoldReportID; let optimisticHoldActionID; let optimisticHoldReportExpenseActionIDs; + let optimisticDuplicatedReportActionIDs; if (!full) { const holdReportOnyxData = getReportFromHoldRequestsOnyxData(chatReport, iouReport, recipient); @@ -10724,6 +10823,7 @@ function getPayMoneyRequestParams({ optimisticHoldReportID = holdReportOnyxData.optimisticHoldReportID; optimisticHoldActionID = holdReportOnyxData.optimisticHoldActionID; optimisticHoldReportExpenseActionIDs = JSON.stringify(holdReportOnyxData.optimisticHoldReportExpenseActionIDs); + optimisticDuplicatedReportActionIDs = JSON.stringify(holdReportOnyxData.optimisticDuplicatedReportActionIDs); } return { @@ -10737,6 +10837,7 @@ function getPayMoneyRequestParams({ optimisticHoldReportID, optimisticHoldActionID, optimisticHoldReportExpenseActionIDs, + optimisticDuplicatedReportActionIDs, ...policyParams, }, optimisticData, @@ -11163,6 +11264,7 @@ function approveMoneyRequest( let optimisticHoldReportID; let optimisticHoldActionID; let optimisticHoldReportExpenseActionIDs; + let optimisticDuplicatedReportActionIDs; if (!full && !!chatReport && !!expenseReport) { const holdReportOnyxData = getReportFromHoldRequestsOnyxData(chatReport, expenseReport, {accountID: expenseReport.ownerAccountID}); @@ -11172,6 +11274,7 @@ function approveMoneyRequest( optimisticHoldReportID = holdReportOnyxData.optimisticHoldReportID; optimisticHoldActionID = holdReportOnyxData.optimisticHoldActionID; optimisticHoldReportExpenseActionIDs = JSON.stringify(holdReportOnyxData.optimisticHoldReportExpenseActionIDs); + optimisticDuplicatedReportActionIDs = JSON.stringify(holdReportOnyxData.optimisticDuplicatedReportActionIDs); } // Remove duplicates violations if we approve the report @@ -11214,6 +11317,7 @@ function approveMoneyRequest( optimisticHoldReportID, optimisticHoldActionID, optimisticHoldReportExpenseActionIDs, + optimisticDuplicatedReportActionIDs, }; playSound(SOUNDS.SUCCESS);