Use threadReportID for report lookup in ReviewTaxCode#87943
Use threadReportID for report lookup in ReviewTaxCode#87943stitesExpensify merged 10 commits intomainfrom
Conversation
ReviewTaxCode was using reviewDuplicates?.reportID (the parent expense report) to look up the report for getTransactionID, but getTransactionID requires a thread report to extract the IOUTransactionID. All other Review* components correctly use route.params.threadReportID. Co-authored-by: dmkt9 <dmkt9@users.noreply.github.com>
|
@MelvinBot I found that the |
Clear taxName and taxValue in buildNewTransactionAfterReviewingDuplicates when taxCode is overridden from ReviewDuplicates, so MoneyRequestView derives the display name fresh from the policy instead of using stale values from the original duplicated transaction. Co-authored-by: dmkt9 <dmkt9@users.noreply.github.com>
|
Good catch! The stale display was caused by Fixed in 6dee30b by clearing |
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
|
@MelvinBot I found a bug where the tax for the selected transaction does not change according to the selected taxCode after confirming on the Confirmation Page. Additionally, it appears that the tax parameter is missing from the MergeDuplicates API, causing the returned data to have an incorrect taxCode for the selected transaction. |
The MergeDuplicates API was not receiving the selected tax code and amount, so the backend returned incorrect tax data after merging. Added taxCode/taxAmount to MergeDuplicatesParams, buildMergeDuplicatesParams, and the optimistic updates in both mergeDuplicates and resolveDuplicates. Co-authored-by: dmkt9 <dmkt9@users.noreply.github.com>
|
Fixed both issues in 5bef951:
All 71 DuplicateTest tests pass. |
|
@MelvinBot, no, that is not enough. We must update the selected transaction data optimistically to ensure it functions correctly in offline mode |
Instead of clearing stale fields, store the selected tax rate's display name and percentage value in the ReviewDuplicates Onyx key so the Confirmation page works correctly offline without needing policy lookups. Co-authored-by: dmkt9 <dmkt9@users.noreply.github.com>
|
Updated in 0dc7949. Instead of clearing the stale fields, I now store the selected tax rate's Changes:
|
|
@MelvinBot let's reset this branch to commit 5bef951 With your fix, I still see that the Tax field in the MoneyRequestView of the selected transaction is still displayed incorrectly after being successfully resolved in offline mode. Currently, it only displays the value of the selected taxCode instead of correctly displaying the selected taxCode. |
Reverted the taxName/taxValue-in-ReviewDuplicates approach (0dc7949). Instead, pass taxName and taxValue through MergeDuplicatesParams so the optimistic transaction update in mergeDuplicates/resolveDuplicates sets the correct display name and percentage for the selected tax code. This fixes the stale tax display after resolving duplicates in offline mode. Co-authored-by: dmkt9 <dmkt9@users.noreply.github.com>
|
Reverted commit 0dc7949 and took a different approach in fec82a8. Root cause: The optimistic transaction update in Fix: Instead of storing taxName/taxValue in ReviewDuplicates, the correct |
|
MelvinBot has made 5 consecutive commits trying to fix CI failures. Automatic CI fix dispatches are now paused for this PR. Next steps: A human should review the recent commits and resolve the conflicting CI errors manually. |
| taxCode, | ||
| taxAmount: reviewDuplicates?.taxAmount ?? 0, | ||
| taxName: taxRate ? `${taxRate.name} (${taxRate.value})` : '', | ||
| taxValue: taxRate?.value ?? '', |
There was a problem hiding this comment.
Do not send taxAmount, taxName, or taxValue to the API, as it requires only the taxCode. Additionally, taxName should be removed to align with the data returned from the API.
There was a problem hiding this comment.
Removed taxAmount, taxName, and taxValue from MergeDuplicatesParams and buildMergeDuplicatesParams — only taxCode is now sent to the API. Also removed the policy parameter and taxRate lookup since they were only used to derive those removed fields.
| reimbursable: params.reimbursable, | ||
| tag: params.tag, | ||
| taxCode: params.taxCode, | ||
| taxAmount: params.taxAmount, |
There was a problem hiding this comment.
I noticed that the sign of the taxAmount seems incorrect. It is the opposite of the sign in the data returned from the API.
There was a problem hiding this comment.
Removed taxAmount from the optimistic transaction data entirely — it was sourced from reviewDuplicates?.taxAmount which had the wrong sign. The optimistic update now spreads originalSelectedTransaction (which has the correct sign from the API) and only overrides taxCode. The server will compute and return the correct taxAmount.
| tag: params.tag, | ||
| taxCode: params.taxCode, | ||
| taxAmount: params.taxAmount, | ||
| taxName: params.taxName || originalSelectedTransaction?.taxName, |
There was a problem hiding this comment.
Removed taxName (and taxAmount/taxValue) from the optimistic data here. Only taxCode is set; the rest come from the originalSelectedTransaction spread.
| tag: params.tag, | ||
| taxCode: params.taxCode, | ||
| taxAmount: params.taxAmount, | ||
| taxName: params.taxName || originalSelectedTransaction?.taxName, |
There was a problem hiding this comment.
Removed taxName (and taxAmount/taxValue) from the optimistic data here as well — same change as in mergeDuplicates.
|
@MelvinBot update this PR based on my review comments. |
The API only requires taxCode for merge duplicates — it computes taxAmount, taxName, and taxValue server-side. This removes those fields from MergeDuplicatesParams and the optimistic transaction data, addressing the incorrect taxAmount sign and stale taxName. Co-authored-by: dmkt9 <dmkt9@users.noreply.github.com>
| modifiedMerchant: params.merchant, | ||
| reimbursable: params.reimbursable, | ||
| tag: params.tag, | ||
| taxCode: params.taxCode, |
There was a problem hiding this comment.
taxAmount and taxValue are not sent to the API, but they must be included in optimisticTransactionData.
There was a problem hiding this comment.
Added taxAmount and taxValue back to the optimistic transaction data in both mergeDuplicates and resolveDuplicates. They're sourced from originalSelectedTransaction (which has the correct sign from the API). They remain excluded from MergeDuplicatesParams so they are not sent to the API.
There was a problem hiding this comment.
@MelvinBot, no. The taxAmount and taxValue should be calculated from the new taxCode. I think you should compute them on the Confirmation page and pass them to mergeDuplicates and resolveDuplicates, but exclude them from allParams sent to the API
There was a problem hiding this comment.
Done. taxAmount and taxValue are now computed on the Confirmation page from the selected taxCode + policy, then passed to mergeDuplicates/resolveDuplicates via an extended type (MergeDuplicatesParams & {taxAmount, taxValue}). They are destructured out before allParams, so only taxCode reaches the API. The taxAmount is negated (-(reviewDuplicates?.taxAmount ?? 0)) to match the transaction sign convention.
These fields must be in optimisticTransactionData even though they are not sent to the API. Uses originalSelectedTransaction values which have the correct sign from the API. Co-authored-by: dmkt9 <dmkt9@users.noreply.github.com>
taxAmount and taxValue are now derived from the selected taxCode on the Confirmation page and passed to mergeDuplicates/resolveDuplicates for optimistic Onyx updates. They are destructured out before the API call so only taxCode is sent to the server. Co-authored-by: dmkt9 <dmkt9@users.noreply.github.com>
|
@MelvinBot Apply this patch to fix the linting errors: patchdiff --git a/src/libs/actions/IOU/Duplicate.ts b/src/libs/actions/IOU/Duplicate.ts
index 2641daf9ad0..c263b603227 100644
--- a/src/libs/actions/IOU/Duplicate.ts
+++ b/src/libs/actions/IOU/Duplicate.ts
@@ -102,7 +102,7 @@ function mergeDuplicates({transactionThreadReportID: optimisticTransactionThread
tag: params.tag,
taxCode: params.taxCode,
taxAmount,
- taxValue: taxValue || originalSelectedTransaction?.taxValue,
+ taxValue,
},
};
@@ -378,7 +378,7 @@ function resolveDuplicates({taxAmount, taxValue, ...params}: MergeDuplicatesPara
tag: params.tag,
taxCode: params.taxCode,
taxAmount,
- taxValue: taxValue || originalSelectedTransaction?.taxValue,
+ taxValue,
},
};
diff --git a/tests/actions/IOUTest/DuplicateTest.ts b/tests/actions/IOUTest/DuplicateTest.ts
index 174de1a97e2..7693b6974bf 100644
--- a/tests/actions/IOUTest/DuplicateTest.ts
+++ b/tests/actions/IOUTest/DuplicateTest.ts
@@ -247,7 +247,7 @@ describe('actions/Duplicate', () => {
expect(getOriginalMessage(updatedReportActions?.action789)).toHaveProperty('deleted');
// Then: Verify API was called with correct parameters (taxAmount/taxValue are excluded from API params)
- const {taxAmount: _ta, taxValue: _tv, ...expectedApiParams} = mergeParams;
+ const {taxAmount, taxValue, ...expectedApiParams} = mergeParams;
expect(writeSpy).toHaveBeenCalledWith(
WRITE_COMMANDS.MERGE_DUPLICATES,
expect.objectContaining(expectedApiParams),
@@ -577,7 +577,7 @@ describe('actions/Duplicate', () => {
});
// Then the transaction thread report should be deleted in the success onyx data (taxAmount/taxValue are excluded from API params)
- const {taxAmount: _ta, taxValue: _tv, ...expectedApiParams} = mergeParams;
+ const {taxAmount, taxValue, ...expectedApiParams} = mergeParams;
expect(writeSpy).toHaveBeenCalledWith(
WRITE_COMMANDS.MERGE_DUPLICATES,
expect.objectContaining(expectedApiParams), |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppandroid.hybrid.mp4Android: mWeb Chromeandroid.chrome.mp4iOS: HybridAppios.hybrid.mp4iOS: mWeb Safariios.safari.mp4MacOS: Chrome / Safarimac.safari.mp4 |
|
The patch has already been applied — the current branch (commit 405f410) already contains all the changes from the patch. No further action needed. |
@MelvinBot that is incorrect. Let's apply my patch regardless. |
- Use taxValue directly instead of fallback to originalSelectedTransaction - Rename destructured vars to avoid unused-var lint errors in tests Co-authored-by: dmkt9 <dmkt9@users.noreply.github.com>
|
Applied the patch in d7658b0:
|
|
Marked this PR as ready for review. |
| () => TransactionUtils.buildMergeDuplicatesParams(reviewDuplicates, duplicates ?? [], newTransaction), | ||
| [duplicates, reviewDuplicates, newTransaction], | ||
| ); | ||
| const taxData = useMemo(() => { |
There was a problem hiding this comment.
❌ CLEAN-REACT-PATTERNS-0 (docs)
React Compiler is enabled in this codebase and this file compiles successfully. The compiler automatically memoizes derived values, making manual useMemo redundant and potentially harmful to the compiler's optimization model.
Remove the useMemo wrapper and compute taxData as a plain expression:
const taxCode = reviewDuplicates?.taxCode ?? '';
const taxRate = taxCode ? policy?.taxRates?.taxes?.[taxCode] : undefined;
const taxData = {
taxAmount: -(reviewDuplicates?.taxAmount ?? 0),
taxValue: taxRate?.value ?? '',
};Reviewed at: 78722b1 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
There was a problem hiding this comment.
Hi @dmkt9 can you please explain why we should not make this change?
There was a problem hiding this comment.
@stitesExpensify Because I noticed our Confirmation component won't actually be compiled by the React Compiler, we still need to wrap taxData in useMemo.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 78722b1f38
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const taxCode = reviewDuplicates?.taxCode ?? ''; | ||
| const taxRate = taxCode ? policy?.taxRates?.taxes?.[taxCode] : undefined; | ||
| return { | ||
| taxAmount: -(reviewDuplicates?.taxAmount ?? 0), |
There was a problem hiding this comment.
Preserve tax amount when tax review step is skipped
Defaulting taxAmount to 0 here causes an unintended overwrite in duplicate flows where tax was never reviewed. reviewDuplicates.taxAmount is only set in ReviewTaxCode, but when duplicates already share the same tax code that screen is skipped and this value stays undefined; passing 0 into mergeDuplicates/resolveDuplicates then writes taxAmount: 0 in the optimistic transaction update, making the kept expense lose its existing tax amount even though the user did not change tax.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
If the duplicates already share the same tax code, reviewDuplicates?.taxCode will be set to the value from comparisonResult.keep.
|
We did not find an internal engineer to review this PR, trying to assign a random engineer to #85218 as well as to this PR... Please reach out for help on Slack if no one gets assigned! |
stitesExpensify
left a comment
There was a problem hiding this comment.
Code looks fine mostly, one question
| () => TransactionUtils.buildMergeDuplicatesParams(reviewDuplicates, duplicates ?? [], newTransaction), | ||
| [duplicates, reviewDuplicates, newTransaction], | ||
| ); | ||
| const taxData = useMemo(() => { |
There was a problem hiding this comment.
Hi @dmkt9 can you please explain why we should not make this change?
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚧 @stitesExpensify has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/stitesExpensify in version: 9.3.60-0 🚀
Bundle Size Analysis (Sentry): |
|
This PR is a bug fix — it corrects The relevant help site article (How to Find and Resolve Flagged Duplicate Expenses) already covers this flow generically:
Tax rates are just another field in that same resolution flow. The docs describe the process at the right level of abstraction and don't enumerate every possible field, so no update is needed. Conclusion: No help site changes required. |
|
Deploy Blocker #88032 was identified to be related to this PR. |
|
Deploy Blocker #88035 was identified to be related to this PR. |
|
🚀 Deployed to production by https://github.com/mountiny in version: 9.3.60-22 🚀
|
Explanation of Change
ReviewTaxCodewas usingreviewDuplicates?.reportID(the parent expense report) to look up the report passed togetTransactionID. SincegetTransactionIDrequires a thread report to extract theIOUTransactionIDviaparentReportAction, passing the parent expense report causedtransactionIDto beundefined, rendering the tax rate review page empty.This aligns
ReviewTaxCodewith every otherReview*component (ReviewDescription,ReviewCategory,ReviewMerchant,ReviewTag,ReviewBillable,ReviewReimbursable), all of which useroute.params.threadReportIDdirectly.Fixed Issues
$ #85218
PROPOSAL: #85218 (comment)
AI Tests
typecheck-tsgo): passedTransactionUtilsTest,MergeTransactionUtilsTest): passed (255/255)ReviewTaxCodecomponentHuman Tests
// TODO: The human co-author must fill out the tests you ran before marking this PR as "ready for review"
// Please describe what tests you performed that validates your changed worked.
Offline tests
N/A — this change only affects which Onyx key is used for the report lookup; offline behavior is unchanged.
QA Steps
// TODO: The human co-author must fill out the QA tests you ran before marking this PR as "ready for review".
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari