[$250] Keep this one button appears only on one of duplicate expenses
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
- Create manual expense in workspace A
- Create same expense in workspace B
- Create another random expense in workspace B
- 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
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 Owner
Current Issue Owner: @chiragsalian
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.
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:
- Are part of a duplicate set but don't have duplicate violations stored in their own violation record
- Have duplicate violations that reference other duplicates, but those duplicates are settled/approved and filtered out
- 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
Job added to Upwork: https://www.upwork.com/jobs/~021994106298652281912
Triggered auto assignment to Contributor-plus team member for initial proposal review - @DylanDylann (External)
This is a BE bug, the TransactionViolation only includes one duplicated transactionID (It should be include both duplicated transactionID)
πππ C+ reviewed
πππ C+ reviewed
Triggered auto assignment to @chiragsalian, see https://stackoverflow.com/c/expensify/questions/7972 for more details.
@chiragsalian Huh... This is 4 days overdue. Who can take care of this?
π£ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? πΈ
@chiragsalian 6 days overdue. This is scarier than being forced to listen to Vogon poetry!
@chiragsalian Kindly bump
@chiragsalian 10 days overdue. I'm getting more depressed than Marvin.
π£ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? πΈ
@chiragsalian 12 days overdue now... This issue's end is nigh!
Sorry my plate has been a bit full. Havent been able to investigate just yet. Might look into this this week.
π£ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? πΈ
@chiragsalian Whoops! This issue is 2 days overdue. Let's get this updated quick!
@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!
@chiragsalian 6 days overdue. This is scarier than being forced to listen to Vogon poetry!
@chiragsalian Now this issue is 8 days overdue. Are you sure this should be a Daily? Feel free to change it!
π£ It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? πΈ
@chiragsalian 10 days overdue. Is anyone even seeing these? Hello?
Issue not reproducible during KI retests. (First week)
Tested today. Im unable to reproduce it as well. Going to close out this issue.