App icon indicating copy to clipboard operation
App copied to clipboard

[$250] Keep this one button appears only on one of duplicate expenses

Open jponikarchuk opened this issue 1 month ago β€’ 21 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.62-2 Reproducible in staging?: Yes Reproducible in production?: Yes If this was caught during regression testing, add the test name, ID and link from BrowserStack: Exploratory Email or phone of affected tester (no customers): [email protected] Issue reported by: Applause Internal Team Device used: Lenovo 80ES / Windows 10 Pro App Component: Chat Report View

Action Performed:

Precondition: User have 2 workspaces, A and B

  1. Create manual expense in workspace A
  2. Create same expense in workspace B
  3. Create another random expense in workspace B
  4. Open the report> Click on Review duplicates button

Expected Result:

Both duplicates should have Keep this one button on preview

Actual Result:

Keep this one button appears only on one of duplicate expenses

Workaround:

Unknown

Platforms:

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

Screenshots/Videos

https://github.com/user-attachments/assets/34123ced-695b-42ed-b9cb-315c2cdc8b4d

View all open jobs on GitHub

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~021994106298652281912
  • Upwork Job ID: 1994106298652281912
  • Last Price Increase: 2025-12-25
Issue OwnerCurrent Issue Owner: @chiragsalian

jponikarchuk avatar Nov 24 '25 07:11 jponikarchuk

Triggered auto assignment to @zanyrenney (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 Nov 24 '25 07:11 melvin-bot[bot]

Proposal: Fix "Keep this one" Button Not Appearing on All Duplicate Expenses

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

When reviewing duplicate expenses on the "Review duplicates" page, the "Keep this one" button only appears on one of the duplicate expenses instead of appearing on all duplicate expenses in the preview.

What is the root cause of that problem?

The issue occurs because the areThereDuplicates condition in TransactionPreview/index.tsx checks if the current transaction has duplicate violations stored. However, when duplicates are detected across different workspaces, not all transactions in the duplicate set may have duplicate violations stored in their own violation records.

The current logic (line 67-73) checks:

const areThereDuplicates = allDuplicateIDs.length > 0 && duplicates.length > 0 && allDuplicateIDs.length === duplicates.length;

This condition fails for transactions that:

  1. Are part of a duplicate set but don't have duplicate violations stored in their own violation record
  2. Have duplicate violations that reference other duplicates, but those duplicates are settled/approved and filtered out
  3. Are displayed on the review page but their violation data doesn't include all the other duplicates

When viewing the review duplicate page, all transactions shown are confirmed duplicates, but the current logic only shows the button if the transaction's own violations contain duplicate references. This causes the button to appear on only one transaction (the one that has the violations stored) rather than all transactions in the duplicate set.

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

File to Modify

File: App/src/components/ReportActionItem/TransactionPreview/index.tsx

Change 1: Add imports for review context access

Location: Line 15 (imports section)

Change: Add getLinkedTransactionID and getReportAction to the existing import from @libs/ReportActionsUtils

// Before:
import {getOriginalMessage, isMoneyRequestAction as isMoneyRequestActionReportActionsUtils} from '@libs/ReportActionsUtils';

// After:
import {getLinkedTransactionID, getOriginalMessage, getReportAction, isMoneyRequestAction as isMoneyRequestActionReportActionsUtils} from '@libs/ReportActionsUtils';

Change 2: Get all duplicate transaction IDs from review page context

Location: After line 61 (after isReviewDuplicateTransactionPage definition)

Change: Add logic to get all duplicate transaction IDs from the review page context

// On the review duplicate page, get all duplicate transaction IDs from the review context
// to check if the current transaction is part of the duplicate set
const [threadReport] = useOnyx(
    isReviewDuplicateTransactionPage && route.params?.threadReportID ? `${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}` : '',
    {canBeMissing: true},
);
const reviewReportAction = isReviewDuplicateTransactionPage ? getReportAction(threadReport?.parentReportID, threadReport?.parentReportActionID) : undefined;
const reviewTransactionID = isReviewDuplicateTransactionPage ? getLinkedTransactionID(reviewReportAction) : undefined;
const reviewTransactionViolations = useTransactionViolations(reviewTransactionID);
const reviewDuplicateTransactionIDs = useMemo(
    () =>
        isReviewDuplicateTransactionPage && reviewTransactionID
            ? (reviewTransactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [])
            : [],
    [isReviewDuplicateTransactionPage, reviewTransactionID, reviewTransactionViolations],
);
const allReviewTransactionIDs = useMemo(
    () => (isReviewDuplicateTransactionPage && reviewTransactionID ? [reviewTransactionID, ...reviewDuplicateTransactionIDs] : []),
    [isReviewDuplicateTransactionPage, reviewTransactionID, reviewDuplicateTransactionIDs],
);
const isTransactionInReviewDuplicateSet = isReviewDuplicateTransactionPage && transaction?.transactionID ? allReviewTransactionIDs.includes(transaction.transactionID) : false;

Why: This gets the thread report from the route params, extracts the primary transaction ID and all its duplicate IDs, then checks if the current transaction is part of that set.

Change 3: Update areThereDuplicates condition

Location: Lines 67-73 (replace existing areThereDuplicates calculation)

Change: Update the condition to check if transaction is in review's duplicate set

// Before:
const areThereDuplicates = allDuplicateIDs.length > 0 && duplicates.length > 0 && allDuplicateIDs.length === duplicates.length;

// After:
// On the review duplicate page, show the button if the transaction is part of the duplicate set being reviewed
// (either it has duplicate violations OR it's in the review's duplicate set)// In other contexts, check that all duplicates are present and valid
const areThereDuplicates = isReviewDuplicateTransactionPage
    ? allDuplicateIDs.length > 0 || isTransactionInReviewDuplicateSet
    : allDuplicateIDs.length > 0 && duplicates.length > 0 && allDuplicateIDs.length === duplicates.length;

Why this works:

  • On the review duplicate page, if a transaction is displayed, it's confirmed to be part of the duplicate set
  • By checking if the transaction is in the review's duplicate set (from the thread report's transaction violations), we can show the button even if the transaction itself doesn't have violations stored
  • In other contexts (not on review page), we keep the existing stricter validation

What alternative solutions did you explore? (Optional)

N/A

abbasifaizan70 avatar Nov 24 '25 09:11 abbasifaizan70

Job added to Upwork: https://www.upwork.com/jobs/~021994106298652281912

melvin-bot[bot] avatar Nov 27 '25 18:11 melvin-bot[bot]

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

melvin-bot[bot] avatar Nov 27 '25 18:11 melvin-bot[bot]

This is a BE bug, the TransactionViolation only includes one duplicated transactionID (It should be include both duplicated transactionID)

Image

πŸŽ€πŸ‘€πŸŽ€ C+ reviewed

DylanDylann avatar Nov 28 '25 03:11 DylanDylann

πŸŽ€πŸ‘€πŸŽ€ C+ reviewed

DylanDylann avatar Nov 28 '25 03:11 DylanDylann

Triggered auto assignment to @chiragsalian, see https://stackoverflow.com/c/expensify/questions/7972 for more details.

melvin-bot[bot] avatar Nov 28 '25 03:11 melvin-bot[bot]

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

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

πŸ“£ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? πŸ’Έ

melvin-bot[bot] avatar Dec 04 '25 16:12 melvin-bot[bot]

@chiragsalian 6 days overdue. This is scarier than being forced to listen to Vogon poetry!

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

@chiragsalian Kindly bump

DylanDylann avatar Dec 09 '25 03:12 DylanDylann

@chiragsalian 10 days overdue. I'm getting more depressed than Marvin.

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

πŸ“£ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? πŸ’Έ

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

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

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

Sorry my plate has been a bit full. Havent been able to investigate just yet. Might look into this this week.

chiragsalian avatar Dec 15 '25 19:12 chiragsalian

πŸ“£ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? πŸ’Έ

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

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

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

@chiragsalian @zanyrenney @DylanDylann this issue is now 4 weeks old, please consider:

  • Finding a contributor to fix the bug
  • Closing the issue if BZ has been unable to add the issue to a VIP or Wave project
  • If you have any questions, don't hesitate to start a discussion in #expensify-open-source

Thanks!

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

@chiragsalian 6 days overdue. This is scarier than being forced to listen to Vogon poetry!

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

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

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

πŸ“£ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? πŸ’Έ

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

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

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

Issue not reproducible during KI retests. (First week)

mvtglobally avatar Dec 27 '25 02:12 mvtglobally

Tested today. Im unable to reproduce it as well. Going to close out this issue.

chiragsalian avatar Dec 30 '25 22:12 chiragsalian