From 02d3a2e687f40d77346baeacb011a262c20e4e94 Mon Sep 17 00:00:00 2001 From: Joel Davies Date: Thu, 11 Dec 2025 10:57:01 +0100 Subject: [PATCH 01/11] fix: create a self-DM on duplicating IOU expense if one doesn't exist --- src/libs/actions/IOU.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 0f95c838d528..b3e34a5d5270 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -6929,12 +6929,6 @@ function duplicateExpenseTransaction( // If no workspace is provided the expense should be unreported if (!targetPolicy) { - const selfDMReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${findSelfDMReportID()}`]; - - if (!selfDMReport) { - return; - } - const trackExpenseParams: CreateTrackExpenseParams = { ...params, participantParams: { @@ -6945,7 +6939,7 @@ function duplicateExpenseTransaction( ...(params.transactionParams ?? {}), validWaypoints: transactionDetails?.waypoints as WaypointCollection | undefined, }, - report: selfDMReport, + report: undefined, isDraftPolicy: false, }; return trackExpense(trackExpenseParams); From 6b38c3ca757499557204b2c5839d4baa70ef71d1 Mon Sep 17 00:00:00 2001 From: Joel Davies Date: Thu, 11 Dec 2025 11:21:36 +0100 Subject: [PATCH 02/11] fix: parse html back to markdown in description --- src/libs/actions/IOU.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index b3e34a5d5270..4260489e8dfa 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -6908,7 +6908,7 @@ function duplicateExpenseTransaction( ...transaction, ...transactionDetails, attendees: transactionDetails?.attendees as Attendee[] | undefined, - comment: transactionDetails?.comment, + comment: Parser.htmlToMarkdown(transactionDetails?.comment ?? ''), created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), customUnitRateID: transaction?.comment?.customUnit?.customUnitRateID, isTestDrive: transaction?.receipt?.isTestDriveReceipt, From 44666528e3ee38fb404ebedf0410e718cccdabea Mon Sep 17 00:00:00 2001 From: Joel Davies Date: Thu, 11 Dec 2025 15:27:46 +0100 Subject: [PATCH 03/11] fix: close menu immediately for single transactions --- src/components/MoneyReportHeader.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 1c218c314368..be65a93a090e 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -1205,7 +1205,6 @@ function MoneyReportHeader({ duplicateExpenseTransaction([transaction]); }, - shouldCloseModalOnSelect: false, }, [CONST.REPORT.SECONDARY_ACTIONS.CHANGE_WORKSPACE]: { text: translate('iou.changeWorkspace'), From 8d6aa4683b2c59a5d8c04c2809392b54948512f8 Mon Sep 17 00:00:00 2001 From: Joel Davies Date: Thu, 11 Dec 2025 17:05:49 +0100 Subject: [PATCH 04/11] fix: remove duplicated split expense's link to the original transaction --- src/libs/actions/IOU.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 4260489e8dfa..0eb703fba379 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -6914,7 +6914,7 @@ function duplicateExpenseTransaction( isTestDrive: transaction?.receipt?.isTestDriveReceipt, merchant: transaction?.modifiedMerchant ? transaction.modifiedMerchant : (transaction?.merchant ?? ''), modifiedAmount: undefined, - originalTransactionID: transaction?.comment?.originalTransactionID, + originalTransactionID: undefined, receipt: undefined, source: undefined, waypoints: transactionDetails?.waypoints as WaypointCollection | undefined, From e396a4104720bc42c30b59361e77f7c8c46fd3d4 Mon Sep 17 00:00:00 2001 From: Joel Davies Date: Thu, 11 Dec 2025 18:02:49 +0100 Subject: [PATCH 05/11] fix: only close the modal if we are on the active policy expense chat --- src/components/MoneyReportHeader.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index be65a93a090e..b1bb01528fc1 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -1205,6 +1205,7 @@ function MoneyReportHeader({ duplicateExpenseTransaction([transaction]); }, + shouldCloseModalOnSelect: activePolicyExpenseChat?.reportID === moneyRequestReport?.reportID, }, [CONST.REPORT.SECONDARY_ACTIONS.CHANGE_WORKSPACE]: { text: translate('iou.changeWorkspace'), From d0945fa8de2d6559f4aa39dd5563382cd8b1b0b9 Mon Sep 17 00:00:00 2001 From: Joel Davies Date: Fri, 12 Dec 2025 10:23:04 +0100 Subject: [PATCH 06/11] fix: re-enable duplicate option --- src/libs/ReportSecondaryActionUtils.ts | 6 +- tests/unit/ReportSecondaryActionUtilsTest.ts | 87 ++++++++++---------- 2 files changed, 45 insertions(+), 48 deletions(-) diff --git a/src/libs/ReportSecondaryActionUtils.ts b/src/libs/ReportSecondaryActionUtils.ts index 3fbbce02710c..aeed84b6009b 100644 --- a/src/libs/ReportSecondaryActionUtils.ts +++ b/src/libs/ReportSecondaryActionUtils.ts @@ -751,8 +751,7 @@ function getSecondaryReportActions({ options.push(CONST.REPORT.SECONDARY_ACTIONS.MERGE); } - // Disabled for now to fix deploy blockers. Will be re-enabled in https://github.com/Expensify/App/pull/77343 - if (isDuplicateAction(report, reportTransactions) && false) { + if (isDuplicateAction(report, reportTransactions)) { options.push(CONST.REPORT.SECONDARY_ACTIONS.DUPLICATE); } @@ -832,8 +831,7 @@ function getSecondaryTransactionThreadActions( options.push(CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.MERGE); } - // Disabled for now to fix deploy blockers. Will be re-enabled in https://github.com/Expensify/App/pull/77343 - if (isDuplicateAction(parentReport, [reportTransaction]) && false) { + if (isDuplicateAction(parentReport, [reportTransaction])) { options.push(CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.DUPLICATE); } diff --git a/tests/unit/ReportSecondaryActionUtilsTest.ts b/tests/unit/ReportSecondaryActionUtilsTest.ts index 7877046063b2..d60c8cc299d3 100644 --- a/tests/unit/ReportSecondaryActionUtilsTest.ts +++ b/tests/unit/ReportSecondaryActionUtilsTest.ts @@ -1654,50 +1654,49 @@ describe('getSecondaryAction', () => { expect(result).not.toContain(CONST.REPORT.SECONDARY_ACTIONS.REMOVE_HOLD); }); - // Will be re-enabled in https://github.com/Expensify/App/pull/77343 - // it('include DUPLICATE option for single-transaction expense report', () => { - // const report = { - // reportID: REPORT_ID, - // type: CONST.REPORT.TYPE.EXPENSE, - // ownerAccountID: EMPLOYEE_ACCOUNT_ID, - // statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, - // stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, - // } as unknown as Report; - - // const TRANSACTION_ID = 'TRANSACTION_ID'; - - // const transaction1 = { - // transactionID: TRANSACTION_ID, - // reportID: REPORT_ID, - // } as unknown as Transaction; - - // const reportActions = [ - // { - // reportActionID: '1', - // actorAccountID: EMPLOYEE_ACCOUNT_ID, - // actionName: CONST.REPORT.ACTIONS.TYPE.IOU, - // originalMessage: { - // IOUTransactionID: TRANSACTION_ID, - // IOUReportID: REPORT_ID, - // }, - // }, - // ] as unknown as ReportAction[]; - - // const policy = {} as unknown as Policy; - - // const result = getSecondaryReportActions({ - // currentUserEmail: EMPLOYEE_EMAIL, - // currentUserAccountID: EMPLOYEE_ACCOUNT_ID, - // report, - // chatReport, - // reportTransactions: [transaction1], - // originalTransaction: {} as Transaction, - // violations: {}, - // policy, - // reportActions, - // }); - // expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.DUPLICATE)).toBe(true); - // }); + it('include DUPLICATE option for single-transaction expense report', () => { + const report = { + reportID: REPORT_ID, + type: CONST.REPORT.TYPE.EXPENSE, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + } as unknown as Report; + + const TRANSACTION_ID = 'TRANSACTION_ID'; + + const transaction1 = { + transactionID: TRANSACTION_ID, + reportID: REPORT_ID, + } as unknown as Transaction; + + const reportActions = [ + { + reportActionID: '1', + actorAccountID: EMPLOYEE_ACCOUNT_ID, + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + originalMessage: { + IOUTransactionID: TRANSACTION_ID, + IOUReportID: REPORT_ID, + }, + }, + ] as unknown as ReportAction[]; + + const policy = {} as unknown as Policy; + + const result = getSecondaryReportActions({ + currentUserEmail: EMPLOYEE_EMAIL, + currentUserAccountID: EMPLOYEE_ACCOUNT_ID, + report, + chatReport, + reportTransactions: [transaction1], + originalTransaction: {} as Transaction, + violations: {}, + policy, + reportActions, + }); + expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.DUPLICATE)).toBe(true); + }); it('does not include DUPLICATE option if there are no transactions', async () => { const report = { From da25e1d9f14a78d92fad081cbd0894ec4c923973 Mon Sep 17 00:00:00 2001 From: Joel Davies Date: Fri, 12 Dec 2025 13:48:44 +0100 Subject: [PATCH 07/11] fix: close the modal based on the active policy's IOU report ID --- src/components/MoneyReportHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 1ff42ff4a83a..68a503f43c2f 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -1218,7 +1218,7 @@ function MoneyReportHeader({ duplicateExpenseTransaction([transaction]); }, - shouldCloseModalOnSelect: activePolicyExpenseChat?.reportID === moneyRequestReport?.reportID, + shouldCloseModalOnSelect: activePolicyExpenseChat?.iouReportID === moneyRequestReport?.reportID, }, [CONST.REPORT.SECONDARY_ACTIONS.CHANGE_WORKSPACE]: { text: translate('iou.changeWorkspace'), From 3a9bf0b83ee8b85419376d30d8441ed715923a7f Mon Sep 17 00:00:00 2001 From: Joel Davies Date: Mon, 15 Dec 2025 17:12:42 +0100 Subject: [PATCH 08/11] fix: immediately remove focus on selecting duplicate expense option --- src/components/PopoverMenu.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 8d20f9261a54..00b616196be9 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -321,6 +321,7 @@ function BasePopoverMenu({ ); } else { selectedItem.onSelected?.(); + setFocusedIndex(-1); } } else { onItemSelected?.(selectedItem, index, event); From b3a6afe12d26c37f9d500fb28721afb4b27cf0d0 Mon Sep 17 00:00:00 2001 From: Joel Davies Date: Tue, 16 Dec 2025 16:00:56 +0100 Subject: [PATCH 09/11] fix: immediately defocus on safari too --- src/components/PopoverMenu.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 00b616196be9..946a01a6760f 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -309,6 +309,10 @@ function BasePopoverMenu({ setEnteredSubMenuIndexes([...enteredSubMenuIndexes, index]); const selectedSubMenuItemIndex = selectedItem?.subMenuItems.findIndex((option) => option.isSelected); setFocusedIndex(selectedSubMenuItemIndex); + } else if (!selectedItem.shouldCloseModalOnSelect) { + onItemSelected?.(selectedItem, index, event); + selectedItem.onSelected?.(); + setFocusedIndex(-1); } else if (selectedItem.shouldCallAfterModalHide && (!isSafari() || shouldAvoidSafariException)) { onItemSelected?.(selectedItem, index, event); if (selectedItem.shouldCloseModalOnSelect !== false) { @@ -319,9 +323,6 @@ function BasePopoverMenu({ undefined, selectedItem.shouldCloseAllModals, ); - } else { - selectedItem.onSelected?.(); - setFocusedIndex(-1); } } else { onItemSelected?.(selectedItem, index, event); From e88488cb8aaff6dd47bc4a917796eea1ce795881 Mon Sep 17 00:00:00 2001 From: Joel Davies Date: Tue, 16 Dec 2025 16:37:34 +0100 Subject: [PATCH 10/11] fix: remove unnecessary condition --- src/components/PopoverMenu.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 946a01a6760f..8d00e7226ef1 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -315,15 +315,13 @@ function BasePopoverMenu({ setFocusedIndex(-1); } else if (selectedItem.shouldCallAfterModalHide && (!isSafari() || shouldAvoidSafariException)) { onItemSelected?.(selectedItem, index, event); - if (selectedItem.shouldCloseModalOnSelect !== false) { - close( - () => { - selectedItem.onSelected?.(); - }, - undefined, - selectedItem.shouldCloseAllModals, - ); - } + close( + () => { + selectedItem.onSelected?.(); + }, + undefined, + selectedItem.shouldCloseAllModals, + ); } else { onItemSelected?.(selectedItem, index, event); selectedItem.onSelected?.(); From f3e2893700a01207b491cb4174859b2976cf03ec Mon Sep 17 00:00:00 2001 From: Joel Davies Date: Wed, 17 Dec 2025 09:42:06 +0100 Subject: [PATCH 11/11] fix: explicitly check shouldCloseModalOnSelect is false Co-authored-by: Vinh Hoang --- src/components/PopoverMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 8d00e7226ef1..88e078d28769 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -309,7 +309,7 @@ function BasePopoverMenu({ setEnteredSubMenuIndexes([...enteredSubMenuIndexes, index]); const selectedSubMenuItemIndex = selectedItem?.subMenuItems.findIndex((option) => option.isSelected); setFocusedIndex(selectedSubMenuItemIndex); - } else if (!selectedItem.shouldCloseModalOnSelect) { + } else if (selectedItem.shouldCloseModalOnSelect === false) { onItemSelected?.(selectedItem, index, event); selectedItem.onSelected?.(); setFocusedIndex(-1);