You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[$175] Duplicating an expense should stay on the source report when that report can accept it #101327
Duplicating an expense ignores the report you are looking at. The duplicate is routed to your default workspace, then to whatever report that workspace chat's iouReportID points at — usually the most recently created report from that chat. If you duplicate an expense while viewing an open, editable report, the copy can land on a different report or on a brand-new one.
That is surprising. People duplicate an expense to get a second copy here, next to the original.
Current behavior
useExpenseActions.ts builds the duplicate with targetPolicy: defaultExpensePolicy and targetReport: getPolicyExpenseChat(accountID, defaultExpensePolicy.id) — the default expense policy, not the source expense's policy.
Duplicate.ts passes that workspace chat to requestMoney, and MoneyRequestBuilder.ts resolves the target as allReports[`report_${chatReport.iouReportID}`] — one pointer, no list, no sorting. With two draft reports on the workspace, the second is never considered.
shouldCreateNewMoneyRequestReport then forces a new report when that pointer is missing, pending delete, or cannot accept transactions — so a third draft can appear instead of reusing an addable one.
Proposed behavior
When the source expense is already on a report and that report can accept the duplicate, target the source report. Fall back to today's default-workspace routing only when it cannot.
Roughly: pass the source expense's reportID (and its policy) as the explicit target when the source report is open or otherwise addable — reuse the same "can this report accept a transaction" check shouldCreateNewMoneyRequestReport already relies on, instead of adding a second definition.
Cases to cover
Source report is submitted, approved, closed, or pending delete → keep current behavior, do not reopen a locked report.
Source expense has no report (self-DM / tracked) → stays unreported, as today.
Source report belongs to a workspace other than your default → the duplicate should follow the source report, not jump workspaces.
Please re-state the problem that we are trying to solve in this issue.
Duplicating an expense while viewing an open expense report puts the copy on a different report, or a new one. It should stay on the report you are viewing.
What is the root cause of that problem?
Both "Duplicate expense" entry points always target the default workspace chat, never the source report:
The report header (useExpenseActions.ts:280-282) passes targetPolicy: defaultExpensePolicy and targetReport: activePolicyExpenseChat.
When the target is a chat, MoneyRequestBuilder.ts:1421-1432 picks whatever chatReport.iouReportID points to. If that report can't take the expense, shouldCreateNewMoneyRequestReport creates a new one.
The action layer already supports a specific report. If report is an expense report, requestMoney sets moneyRequestReportID to it (TrackExpense.ts:1741-1743). The distance and per diem paths do the same. This is how "Add expense" inside a report works. The hooks just never pass the source report.
What changes do you think we should make in order to solve the problem?
Choose the target in the two hooks. Duplicate.ts needs no changes.
Add canDuplicateIntoSourceReport to useExpenseActions and MoneyRequestHeaderSecondaryActions. It is true when all of these hold:
the source report is an expense report and is not archived
its chat report is loaded and not archived
its policy is accessible (the same check "Duplicate report" uses)
If true, use the source report and its policy as the target. Otherwise keep the default workspace chat and default policy.
Point everything that depends on the target at the chosen policy and report: categories, tags, policyTagList, participants, and billing restriction checks.
Compare the per diem cross-workspace block (useExpenseActions.ts:230) against the target policy, not the default one.
Submitted, approved, closed, or pending-delete source: the check fails, so routing stays as it is today.
Self-DM or tracked source: there is no expense report, so the copy stays unreported through trackExpense.
Source on a non-default workspace: the copy follows the source report and policy.
Tests:
In tests/actions/IOUTest/DuplicateTest.ts, pass an open expense report as targetReport. Assert that REQUEST_MONEY uses that report's iouReportID.
Add a useExpenseActions hook test with Duplicate mocked. An open source report should target itself. An approved source report should fall back to the default chat.
What alternative solutions did you explore? (Optional)
Pass existingIOUReport instead: this skips the one-to-two-expenses handling that requestMoney runs when it gets a report.
Apply the same routing to bulk duplicate in the report selection toolbar: its iouReportID patching in Duplicate.ts assumes the target is a chat, so it's better handled as a follow-up.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/101327) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
⚠️ Looks like this issue was linked to a Deploy Blocker #103866
@thelullabyy this issue and/or PR mention a deploy blocker or a regression. Before the payment date, post one of the three options in step 20 of the C+ doc: it was a regression, it was not, or you did not review the offending PR. If you don't post, the default is to reduce payment.
changed the title [-][Due for payment 2026-10-21] [$175] Duplicating an expense should stay on the source report when that report can accept it[/-][+][$175] Duplicating an expense should stay on the source report when that report can accept it[/+]on Oct 22, 2026
Activity
melvin-bot commentedon Sep 16, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @thelullabyy (
External)melvin-bot commentedon Sep 16, 2026
Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.
melvin-bot commentedon Sep 21, 2026
@thelullabyy Eep! 4 days overdue now. Issues have feelings too...
melvin-bot commentedon Sep 23, 2026
@thelullabyy Still overdue 6 days?! Let's take care of this!
melvin-bot commentedon Sep 25, 2026
@thelullabyy Now this issue is 8 days overdue. Are you sure this should be a Daily? Feel free to change it!
thelullabyy commentedon Sep 27, 2026
@MelvinBot Please investigate and drop proposal
MelvinBot commentedon Sep 27, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
Duplicating an expense while viewing an open expense report puts the copy on a different report, or a new one. It should stay on the report you are viewing.
What is the root cause of that problem?
Both "Duplicate expense" entry points always target the default workspace chat, never the source report:
useExpenseActions.ts:280-282) passestargetPolicy: defaultExpensePolicyandtargetReport: activePolicyExpenseChat.MoneyRequestHeaderSecondaryActions.tsx:295-297) does the same.When the target is a chat,
MoneyRequestBuilder.ts:1421-1432picks whateverchatReport.iouReportIDpoints to. If that report can't take the expense,shouldCreateNewMoneyRequestReportcreates a new one.The action layer already supports a specific report. If
reportis an expense report,requestMoneysetsmoneyRequestReportIDto it (TrackExpense.ts:1741-1743). The distance and per diem paths do the same. This is how "Add expense" inside a report works. The hooks just never pass the source report.What changes do you think we should make in order to solve the problem?
Choose the target in the two hooks.
Duplicate.tsneeds no changes.Add
canDuplicateIntoSourceReporttouseExpenseActionsandMoneyRequestHeaderSecondaryActions. It is true when all of these hold:!shouldCreateNewMoneyRequestReport(sourceReport, chatReport, false, isASAPSubmitBetaEnabled, rules, CONST.IOU.ACTION.CREATE, true)Reusing
shouldCreateNewMoneyRequestReport(it wrapscanAddTransaction) means the hook and the builder always agree. No second "addable" definition is needed.If true, use the source report and its policy as the target. Otherwise keep the default workspace chat and default policy.
Point everything that depends on the target at the chosen policy and report: categories, tags,
policyTagList, participants, and billing restriction checks.Compare the per diem cross-workspace block (
useExpenseActions.ts:230) against the target policy, not the default one.Close the menu when the copy lands on the viewed report: OR the new flag into
shouldDuplicateCloseModalOnSelect.This covers each case in the issue:
trackExpense.Tests:
tests/actions/IOUTest/DuplicateTest.ts, pass an open expense report astargetReport. Assert thatREQUEST_MONEYuses that report'siouReportID.useExpenseActionshook test withDuplicatemocked. An open source report should target itself. An approved source report should fall back to the default chat.What alternative solutions did you explore? (Optional)
existingIOUReportinstead: this skips the one-to-two-expenses handling thatrequestMoneyruns when it gets a report.iouReportIDpatching inDuplicate.tsassumes the target is a chat, so it's better handled as a follow-up.Related issue
[Due for payment 2026-09-24] [Exploratory] Expense report- Create new expense added to new report instead of current draft report (closed) has the same routing gap in the manual create path.
Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/101327)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run
melvin-bot commentedon Sep 30, 2026
@thelullabyy Whoops! This issue is 2 days overdue. Let's get this updated quick!
24 remaining items
melvin-bot commentedon Oct 20, 2026
Triggered auto assignment to @mallenexpensify (
Awaiting Payment)melvin-bot commentedon Oct 20, 2026
Payment Summary
Resolving PRs:
Keep duplicated expenses on the source report when it can accept them #103072
Reviewer: @thelullabyy owed $175 via NewDot
BugZero Checklist (@mallenexpensify)
mallenexpensify commentedon Oct 21, 2026
@thelullabyy this issue and/or PR mention a deploy blocker or a regression. Before the payment date, post one of the three options in step 20 of the C+ doc: it was a regression, it was not, or you did not review the offending PR. If you don't post, the default is to reduce payment.
thelullabyy commentedon Oct 22, 2026
Hi @mallenexpensify It is not ready for payment yet, we still need to fix this regression before payment
[-][Due for payment 2026-10-21] [$175] Duplicating an expense should stay on the source report when that report can accept it[/-][+][$175] Duplicating an expense should stay on the source report when that report can accept it[/+]mallenexpensify commentedon Oct 22, 2026
Please comment and tag me if/when payment is due, thx