Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d06256e
Fix: Optimistically copy submission/approval related report actions
mohammadjafarinejad Dec 18, 2025
b399600
Fix: Update action filtering to match backend's WORKFLOW_ACTIONS list
mohammadjafarinejad Dec 21, 2025
72830f1
Merge branch 'main' into fix/74575
mohammadjafarinejad Dec 21, 2025
8f11654
Refactor: Remove unused action imports from IOU actions
mohammadjafarinejad Dec 21, 2025
a08e2fb
fix prettier
mohammadjafarinejad Dec 21, 2025
5c256a9
Refactor: Rename and update copySubmissionApprovalActionsForReport fu…
mohammadjafarinejad Dec 23, 2025
8f03f1e
chore: fix prettier
mohammadjafarinejad Dec 23, 2025
51ed519
Merge branch 'Expensify:main' into fix/74575
mohammadjafarinejad Dec 23, 2025
5c53ceb
Refactor: Enhance type definitions in getDuplicateActionsForPartialRe…
mohammadjafarinejad Dec 23, 2025
e228216
revert removed onyx update
mohammadjafarinejad Dec 23, 2025
083840c
Refactor: Add duplicatedReportActionIDs to PayMoneyRequestParams and …
mohammadjafarinejad Dec 26, 2025
e388a4a
Refactor: Rename duplicatedReportActionIDs to optimisticDuplicatedRep…
mohammadjafarinejad Dec 26, 2025
5754efb
chore : prettier
mohammadjafarinejad Dec 26, 2025
53de75a
merge main
hungvu193 Jan 14, 2026
22eeee8
only copy report action for approval flow
hungvu193 Jan 14, 2026
e2febf0
correct params
hungvu193 Jan 14, 2026
e38ebb4
correct passed value to the API
hungvu193 Jan 14, 2026
9d9aa1c
remove console
hungvu193 Jan 14, 2026
c3216eb
merge main
hungvu193 Jan 14, 2026
940de8f
sync submodule
hungvu193 Jan 14, 2026
d4f6b36
correct type for OptimisticReportActionCopyIDs
hungvu193 Jan 14, 2026
9a19fb1
remove debug console
hungvu193 Jan 14, 2026
f5822de
remove unecessary code
hungvu193 Jan 14, 2026
3abfc45
update variable comment
hungvu193 Jan 14, 2026
24786fc
correct params
hungvu193 Jan 14, 2026
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
8 changes: 8 additions & 0 deletions src/libs/API/parameters/ApproveMoneyRequestParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ type ApproveMoneyRequestParams = {
* }>
*/
optimisticHoldReportExpenseActionIDs?: string;
/**
* Stringified JSON object of type Record<string, string> with the following structure:
* {
* [oldReportActionID]: optimisticReportActionID,
* }
* where optimisticReportActionID is the optimistic report action ID and oldReportActionID is the old report action ID
*/
optimisticReportActionCopyIDs?: string;

/** The optimistic action ID for the report created for unapproved transactions */
optimisticCreatedReportForUnapprovedTransactionsActionID?: string;
Expand Down
105 changes: 105 additions & 0 deletions src/libs/actions/IOU/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,7 @@
};

let allPersonalDetails: OnyxTypes.PersonalDetailsList = {};
Onyx.connect({

Check warning on line 744 in src/libs/actions/IOU/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
callback: (value) => {
allPersonalDetails = value ?? {};
Expand Down Expand Up @@ -840,7 +840,7 @@
};

let allTransactions: NonNullable<OnyxCollection<OnyxTypes.Transaction>> = {};
Onyx.connect({

Check warning on line 843 in src/libs/actions/IOU/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.TRANSACTION,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -854,7 +854,7 @@
});

let allTransactionDrafts: NonNullable<OnyxCollection<OnyxTypes.Transaction>> = {};
Onyx.connect({

Check warning on line 857 in src/libs/actions/IOU/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -863,7 +863,7 @@
});

let allTransactionViolations: NonNullable<OnyxCollection<OnyxTypes.TransactionViolations>> = {};
Onyx.connect({

Check warning on line 866 in src/libs/actions/IOU/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -877,7 +877,7 @@
});

let allNextSteps: NonNullable<OnyxCollection<OnyxTypes.ReportNextStepDeprecated>> = {};
Onyx.connect({

Check warning on line 880 in src/libs/actions/IOU/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.NEXT_STEP,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -886,7 +886,7 @@
});

let allReports: OnyxCollection<OnyxTypes.Report>;
Onyx.connect({

Check warning on line 889 in src/libs/actions/IOU/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -895,7 +895,7 @@
});

let allReportNameValuePairs: OnyxCollection<OnyxTypes.ReportNameValuePairs>;
Onyx.connect({

Check warning on line 898 in src/libs/actions/IOU/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -905,7 +905,7 @@

let userAccountID = -1;
let currentUserEmail = '';
Onyx.connect({

Check warning on line 908 in src/libs/actions/IOU/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (value) => {
currentUserEmail = value?.email ?? '';
Expand All @@ -914,7 +914,7 @@
});

let deprecatedCurrentUserPersonalDetails: OnyxEntry<OnyxTypes.PersonalDetails>;
Onyx.connect({

Check warning on line 917 in src/libs/actions/IOU/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
callback: (value) => {
deprecatedCurrentUserPersonalDetails = value?.[userAccountID] ?? undefined;
Expand All @@ -922,7 +922,7 @@
});

let allReportActions: OnyxCollection<OnyxTypes.ReportActions>;
Onyx.connect({

Check warning on line 925 in src/libs/actions/IOU/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (actions) => {
Expand Down Expand Up @@ -9299,6 +9299,95 @@
return {holdReportActions, holdTransactions};
}

type OptimisticReportActionCopyIDs = Record<string, string>;

/**
* 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<OnyxUpdate<typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS>>,
Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS>>,
Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS>>,
OptimisticReportActionCopyIDs,
] {
const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS>> = [];
const successData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS>> = [];
const failureData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS>> = [];
const optimisticReportActionCopyIDs: OptimisticReportActionCopyIDs = {};

if (!sourceReportID || !targetReportID) {
return [optimisticData, successData, failureData, optimisticReportActionCopyIDs];
}

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<string, OnyxTypes.ReportAction> = {};
const copiedActionsSuccess: OnyxCollection<NullishDeep<ReportAction>> = {};
const copiedActionsFailure: Record<string, null> = {};

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;
optimisticReportActionCopyIDs[action.reportActionID] = newActionID;
}
}

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, optimisticReportActionCopyIDs];
}

function getReportFromHoldRequestsOnyxData({
chatReport,
iouReport,
Expand All @@ -9318,6 +9407,7 @@
optimisticHoldActionID: string;
optimisticCreatedReportForUnapprovedTransactionsActionID: string | undefined;
optimisticHoldReportExpenseActionIDs: OptimisticHoldReportExpenseActionID[];
optimisticReportActionCopyIDs: OptimisticReportActionCopyIDs;
optimisticData: OnyxUpdate[];
successData: OnyxUpdate[];
failureData: OnyxUpdate[];
Expand Down Expand Up @@ -9562,6 +9652,17 @@
},
];

// Copy submission/approval actions to the new report
const [copiedActionsOptimistic, copiedActionsSuccess, copiedActionsFailure, optimisticReportActionCopyIDs] = getDuplicateActionsForPartialReport(
iouReport?.reportID,
optimisticExpenseReport.reportID,
);
// Only copy the report action for approval flow
if (isApprovalFlow && !isEmptyObject(optimisticReportActionCopyIDs)) {
optimisticData.push(...copiedActionsOptimistic);
successData.push(...copiedActionsSuccess);
failureData.push(...copiedActionsFailure);
}
// add optimistic system message explaining the created report for unapproved transactions
if (isApprovalFlow && optimisticCreatedReportForUnapprovedAction) {
optimisticData.push({
Expand Down Expand Up @@ -9600,6 +9701,7 @@
successData,
optimisticHoldReportID: optimisticExpenseReport.reportID,
optimisticHoldReportExpenseActionIDs,
optimisticReportActionCopyIDs,
};
}

Expand Down Expand Up @@ -10305,6 +10407,7 @@
let optimisticHoldReportID;
let optimisticHoldActionID;
let optimisticHoldReportExpenseActionIDs;
let optimisticReportActionCopyIDs;
let optimisticCreatedReportForUnapprovedTransactionsActionID;
if (!full && !!chatReport && !!expenseReport) {
const originalCreated = getReportOriginalCreationTimestamp(expenseReport);
Expand All @@ -10324,6 +10427,7 @@
optimisticHoldActionID = holdReportOnyxData.optimisticHoldActionID;
optimisticCreatedReportForUnapprovedTransactionsActionID = holdReportOnyxData.optimisticCreatedReportForUnapprovedTransactionsActionID;
optimisticHoldReportExpenseActionIDs = JSON.stringify(holdReportOnyxData.optimisticHoldReportExpenseActionIDs);
optimisticReportActionCopyIDs = JSON.stringify(holdReportOnyxData.optimisticReportActionCopyIDs);
}

// Remove duplicates violations if we approve the report
Expand Down Expand Up @@ -10366,6 +10470,7 @@
optimisticHoldReportID,
optimisticHoldActionID,
optimisticHoldReportExpenseActionIDs,
optimisticReportActionCopyIDs,
optimisticCreatedReportForUnapprovedTransactionsActionID,
};

Expand Down
Loading