App icon indicating copy to clipboard operation
App copied to clipboard

Wrong system message after remove expense from report

Open IuliiaHerets opened this issue 1 month ago • 7 comments

If you haven’t already, check out our contributing guidelines for onboarding and email [email protected] to request to join our Slack channel!


Version Number: v9.2.75-0 Reproducible in staging?: Yes Reproducible in production?: Yes If this was caught during regression testing, add the test name, ID and link from BrowserStack: https://github.com/Expensify/App/pull/76209 Issue reported by: Applause Internal Team Bug source: Pull Request QA execution Device used: MacOS Monterey 12.7.4 / Chrome App Component: Other

Action Performed:

  1. Go to workspace chat.
  2. Create an expense.
  3. Open the transaction thread.
  4. Click Report.
  5. Click Remove from report.
  6. Go to self DM.
  7. Open the unreported expense.

Expected Result:

System message "Moved this expense to your personal space" shown

Actual Result:

System message "Moved this expense from " shown

Workaround:

Unknown

Platforms:

  • [x] Android: App
  • [ ] Android: mWeb Chrome
  • [x] iOS: App
  • [ ] iOS: mWeb Safari
  • [ ] iOS: mWeb Chrome
  • [x] Windows: Chrome
  • [ ] MacOS: Chrome / Safari
  • [ ] MacOS: Desktop

Screenshots/Videos

https://github.com/user-attachments/assets/cb91b859-a2d8-4f38-8e66-37feb5bb0c2b

View all open jobs on GitHub

IuliiaHerets avatar Dec 11 '25 09:12 IuliiaHerets

Triggered auto assignment to @muttmuure (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details. Please add this bug to a GH project, as outlined in the SO.

melvin-bot[bot] avatar Dec 11 '25 09:12 melvin-bot[bot]

Proposal

Please re-state the problem that we are trying to solve in this issue.

When removing an expense from a report, the system message shows "Moved this expense from " (incomplete) instead of "Moved this expense to your personal space".

What is the root cause of that problem?

The issue is in the getUnreportedTransactionMessage function in App/src/libs/ReportUtils.ts (around line 7091).

Current problematic logic:

function getUnreportedTransactionMessage(action: ReportAction) {
    const movedTransactionOriginalMessage = getOriginalMessage(action) ?? {};
    const {fromReportID} = movedTransactionOriginalMessage as OriginalMessageMovedTransaction;

    const fromReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${fromReportID}`];

    // eslint-disable-next-line @typescript-eslint/no-deprecated
    const reportName = getReportName(fromReport) ?? fromReport?.reportName ?? '';

    let reportUrl = getReportURLForCurrentContext(fromReportID);

    if (fromReportID === CONST.REPORT.UNREPORTED_REPORT_ID) {
        reportUrl = `${environmentURL}/r/${findSelfDMReportID()}`;
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        return translateLocal('iou.unreportedTransaction', {
            reportUrl,
        });
    }

    // eslint-disable-next-line @typescript-eslint/no-deprecated
    return translateLocal('iou.movedTransactionFrom', {
        reportUrl,
        reportName,
    });
}

Why this happens:

  1. When an expense is removed from a report, an UNREPORTED_TRANSACTION action is created via buildOptimisticUnreportedTransactionAction with fromReportID set to the report the expense was removed from
  2. When displaying the action, getUnreportedTransactionMessage is called
  3. The function checks if fromReportID === CONST.REPORT.UNREPORTED_REPORT_ID (line 7102)
  4. Since the expense is being removed FROM a report (not FROM personal space), this condition is false
  5. The function then tries to retrieve the report name from allReports using fromReportID
  6. If the report doesn't exist in allReports or getReportName returns an empty value, reportName becomes an empty string
  7. The function calls translateLocal('iou.movedTransactionFrom', {reportUrl, reportName}) with an empty reportName
  8. The translation movedTransactionFrom returns "moved this expense from " (incomplete) because it expects a non-empty reportName

The fundamental flaw:

The UNREPORTED_TRANSACTION action type indicates that an expense was moved TO personal space (unreported), not FROM somewhere. The function incorrectly tries to show "moved from {reportName}" when fromReportID is not UNREPORTED_REPORT_ID. The function should always show "moved this expense to your personal space" for UNREPORTED_TRANSACTION actions, because the action type itself inherently represents moving TO personal space.

What changes do you think we should make in order to solve the problem?

Simplify the getUnreportedTransactionMessage function to always return the "moved to personal space" message, since UNREPORTED_TRANSACTION actions inherently mean the expense was moved to personal space.

File: App/src/libs/ReportUtils.ts

Location: getUnreportedTransactionMessage function (around line 7091)

Change the function from:

function getUnreportedTransactionMessage(action: ReportAction) {
    const movedTransactionOriginalMessage = getOriginalMessage(action) ?? {};
    const {fromReportID} = movedTransactionOriginalMessage as OriginalMessageMovedTransaction;

    const fromReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${fromReportID}`];

    // eslint-disable-next-line @typescript-eslint/no-deprecated
    const reportName = getReportName(fromReport) ?? fromReport?.reportName ?? '';

    let reportUrl = getReportURLForCurrentContext(fromReportID);

    if (fromReportID === CONST.REPORT.UNREPORTED_REPORT_ID) {
        reportUrl = `${environmentURL}/r/${findSelfDMReportID()}`;
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        return translateLocal('iou.unreportedTransaction', {
            reportUrl,
        });
    }

    // eslint-disable-next-line @typescript-eslint/no-deprecated
    return translateLocal('iou.movedTransactionFrom', {
        reportUrl,
        reportName,
    });
}

To:

function getUnreportedTransactionMessage(action: ReportAction) {
    const reportUrl = `${environmentURL}/r/${findSelfDMReportID()}`;
    // eslint-disable-next-line @typescript-eslint/no-deprecated
    return translateLocal('iou.unreportedTransaction', {
        reportUrl,
    });
}

Why this works:

  1. Simplified logic: Since UNREPORTED_TRANSACTION always means the expense was moved TO personal space, we don't need to check the fromReportID
  2. Consistent message: Always shows "moved this expense to your personal space" which accurately describes what happened
  3. No dependency on report existence: Removes the dependency on fromReport existing in allReports, which was causing the incomplete message
  4. Matches action semantics: The action type UNREPORTED_TRANSACTION is specifically for moving expenses to personal space, so the message should reflect that destination

Translation used: iou.unreportedTransaction renders as: "moved this expense to your personal space" (with a link to the self DM)

What alternative solutions did you explore? (Optional)

None

Demo

Image Image

abbasifaizan70 avatar Dec 11 '25 09:12 abbasifaizan70

@muttmuure Huh... This is 4 days overdue. Who can take care of this?

melvin-bot[bot] avatar Dec 17 '25 00:12 melvin-bot[bot]

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

melvin-bot[bot] avatar Dec 18 '25 23:12 melvin-bot[bot]

@muttmuure 10 days overdue. Is anyone even seeing these? Hello?

melvin-bot[bot] avatar Dec 23 '25 00:12 melvin-bot[bot]

@muttmuure 12 days overdue now... This issue's end is nigh!

melvin-bot[bot] avatar Dec 24 '25 23:12 melvin-bot[bot]

@muttmuure this issue was created 2 weeks ago. Are we close to a solution? Let's make sure we're treating this as a top priority. Don't hesitate to create a thread in #expensify-open-source to align faster in real time. Thanks!

melvin-bot[bot] avatar Dec 25 '25 21:12 melvin-bot[bot]

This issue has not been updated in over 14 days. @muttmuure eroding to Weekly issue.

melvin-bot[bot] avatar Jan 01 '26 00:01 melvin-bot[bot]