Skip to content

[$175] Duplicating an expense should stay on the source report when that report can accept it #101327

Open

Description

@melvin-bot

Problem

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.

Related

Reported in Slack.

Upwork Automation - Do Not Edit

Activity

  1. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Sep 16, 2026
  2. self-assigned this
    on Sep 16, 2026
  3. melvin-bot commented on Sep 16, 2026

    @melvin-bot
    Author

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @thelullabyy (External)

  4. melvin-bot commented on Sep 16, 2026

    @melvin-bot
    Author

    Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.

  5. melvin-bot commented on Sep 21, 2026

    @melvin-bot
    Author

    @thelullabyy Eep! 4 days overdue now. Issues have feelings too...

  6. melvin-bot commented on Sep 23, 2026

    @melvin-bot
    Author

    @thelullabyy Still overdue 6 days?! Let's take care of this!

  7. melvin-bot commented on Sep 25, 2026

    @melvin-bot
    Author

    @thelullabyy Now this issue is 8 days overdue. Are you sure this should be a Daily? Feel free to change it!

  8. thelullabyy commented on Sep 27, 2026

    @thelullabyy
    Contributor

    @MelvinBot Please investigate and drop proposal

  9. MelvinBot commented on Sep 27, 2026

    @MelvinBot
    Contributor

    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:

    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.

    1. 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)
      • !shouldCreateNewMoneyRequestReport(sourceReport, chatReport, false, isASAPSubmitBetaEnabled, rules, CONST.IOU.ACTION.CREATE, true)

      Reusing shouldCreateNewMoneyRequestReport (it wraps canAddTransaction) means the hook and the builder always agree. No second "addable" definition is needed.

    2. If true, use the source report and its policy as the target. Otherwise keep the default workspace chat and default policy.

    3. Point everything that depends on the target at the chosen policy and report: categories, tags, policyTagList, participants, and billing restriction checks.

    4. Compare the per diem cross-workspace block (useExpenseActions.ts:230) against the target policy, not the default one.

    5. Close the menu when the copy lands on the viewed report: OR the new flag into shouldDuplicateCloseModalOnSelect.

    This covers each case in the issue:

    • 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.
    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

  10. melvin-bot commented on Sep 30, 2026

    @melvin-bot
    Author

    @thelullabyy Whoops! This issue is 2 days overdue. Let's get this updated quick!

  11. 24 remaining items

  12. added
    Awaiting PaymentAuto-added when associated PR is deployed to production
    and removed on Oct 18, 2026
  13. melvin-bot commented on Oct 20, 2026

    @melvin-bot
    Author

    Triggered auto assignment to @mallenexpensify (Awaiting Payment)

  14. melvin-bot commented on Oct 20, 2026

    @melvin-bot
    Author

    Payment Summary

    Resolving PRs:

    BugZero Checklist (@mallenexpensify)

    • I have confirmed assignees, roles, and Upwork contracts look correct
    • I have paid out Upwork contracts / manual NewDot requests
    • [BugZero Assignee] I have created a GH issue for creating/updating the regression test once above steps have been agreed upon
  15. mallenexpensify commented on Oct 21, 2026

    @mallenexpensify
    Contributor

    ⚠️ 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.

  16. thelullabyy commented on Oct 22, 2026

    @thelullabyy
    Contributor

    Hi @mallenexpensify It is not ready for payment yet, we still need to fix this regression before payment

  17. 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
  18. removed their assignment
    on Oct 22, 2026
  19. mallenexpensify commented on Oct 22, 2026

    @mallenexpensify
    Contributor

    Please comment and tag me if/when payment is due, thx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

DailyKSv2ExternalAdded to denote the issue can be worked on by a contributorReviewingHas a PR in review

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions