You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Due for payment 2026-10-20] [Exploratory] Web - Taxes - Inconsistent tax amount display on report with negative expenses #98098
Preconditions:
User is an Admin of a workspace
Tax settings are enabled
User has created a report with a negative expense and a positive expense with a non-zero tax rate
Open the New Dot app
Navigate to the Workspace chat
Click on the report with 2 expenses
Scroll horizontally to the tax section of the negative expense
Click on the negative expense to open the details
Expected Result:
The Tax amount should display the same on the expense detail and on the report view
Actual Result:
The Tax amount display the positive on the expense detail and as a negative amount on the report view
Updated 2026-09-11: Rewritten to the solution agreed in this thread — use getTaxAmount and drop the ternary, rather than the sign-reconciliation approach originally proposed. Line references refreshed to b060a99.
Proposal
What is the root cause of that problem?
The expense details page throws away the sign of the tax; the report table keeps it.
On expense reports, taxAmount is stored in Onyx with the opposite sign of what we display, so a negative expense (refund) has a positive stored taxAmount. getTaxAmount() flips it back for display.
Expense details — MoneyRequestView.tsx:371-374 wraps that same already-correct value in Math.abs() → shows $1.82.
The Math.abs() is the bug. trjExpensifyconfirmed Classic shows the refund's tax as negative, and that's the semantic the rest of the codebase already assumes.
Where the Math.abs came from
Commit 23ec1dd ("Add Math abs for tax formatting", 2024-06-28), part of #43519 for #42114 — QA had reported tax rendering as negative while moving a track expense into a workspace. The author's stated reason, verbatim:
I resolved the conflicts and pushed a fix for the taxes showing negative. I don't think taxes will ever be negative, so I added a Math.abs
So it's a blanket assumption applied to fix one flow; negative expenses were never considered. The sibling Math.abs from that same PR was later narrowed to exactly the flow it was for — useTaxAmount.ts:83 gates it on isMovingTransactionFromTrackExpense. The MoneyRequestView one never got that treatment.
Why the negative (table) value is the correct one
The Total column in the same row renders the refund as negative, via the same sign convention.
The report-level tax total addsgetTaxAmount(transaction, true) per transaction (ReportUtils.ts:4757, :4770-4775). A refund's tax only reduces the report tax total if it is negative.
The details page is inconsistent with itself: its own Amount field renders the signed value (:355), so you see Amount -$20.00 next to Tax $1.82.
What changes do you think we should make in order to solve the problem?
transactionTaxAmountis alreadygetTaxAmount() — getTransactionDetails derives it that way (ReportUtils.ts:5022) and is called with displayTransaction, which already resolves updatedTransaction ?? onyxTransaction (:236 → :318 → :340). So the updatedTransaction?.taxAmount !== undefined branch is redundant and wrong — it reads the raw stored (inverted-sign) value.
Why dropping the if branch is safe, and why keeping it would be inconsistent
Both of those flows also store taxAmount in the inverted convention, so getTaxAmount is correct for them too: the duplicate page negates explicitly (:96), and the merge path computes it from the raw stored amount through the sign-preserving calculateTaxAmount (MergeTransactionUtils.ts:804).
Keep the ternary and fix only the else, and merge/duplicate still renders an unsigned tax next to a signed amount — the same inconsistency, just moved.
convertedTaxAmount carries the same inverted sign — the report tax total subtracts it (ReportUtils.ts:4774) — so its Math.abs hides the identical bug for foreign-currency expenses. There is no getter for it yet; cleanest is a getConvertedTaxAmount in TransactionUtils mirroring getTaxAmount, exposed through getTransactionDetails alongside convertedAmount, rather than negating inline.
What alternative solutions did you explore? (Optional)
Making TaxCell show Math.abs() so the table matches the details page. Rejected: it would put a positive tax next to a negative Total in the same row, and would no longer match the report's own tax total. trjExpensify and Classic both confirm negative is the correct display.
Adjacent defect found while investigating (separate from this issue)
The tax edit page renders and submits an absolute value (DynamicIOURequestStepTaxAmountPage.tsx), which then gets negated on store — so editing a refund's tax looks like it silently flips its sign. Traced through the optimistic path only, not confirmed end-to-end against the backend response.
Verification notes
Static analysis against b060a99. I could not drive the app in a browser in this run, so the rendered strings are unconfirmed by me — but the reporters' videos and the code path agree.
Note the Tax column is only rendered by the wide table row, so the mismatch is visible on desktop/wide layouts.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/98098) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
changed the title [-][Exploratory] Web - Taxes - Inconsistent tax amount display on report with negative expenses[/-][+][Due for payment 2026-10-20] [Exploratory] Web - Taxes - Inconsistent tax amount display on report with negative expenses[/+]on Oct 13, 2026
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.7-3 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
If no regressions arise, payment will be issued on 2026-10-20. 🎊
The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the Contributor+ Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]. If no checklist is needed for this issue, you can click: [no checklist button]
Activity
applause-bot commentedon Aug 9, 2026
While Applause doesn't embed attachments for this bug, here are the links for quick view:
https://platform.applause.com/services/links/v1/external/8faf6eae15e6a2d0d07cc4527264c3a803e423af452b530ebc7598cfe8dc991d
MelvinBot commentedon Sep 5, 2026
Updated 2026-09-11: Rewritten to the solution agreed in this thread — use
getTaxAmountand drop the ternary, rather than the sign-reconciliation approach originally proposed. Line references refreshed tob060a99.Proposal
What is the root cause of that problem?
The expense details page throws away the sign of the tax; the report table keeps it.
On expense reports,
taxAmountis stored in Onyx with the opposite sign of what we display, so a negative expense (refund) has a positive storedtaxAmount.getTaxAmount()flips it back for display.TaxCell.tsx:16rendersgetTaxAmount()as-is → shows-$1.82.MoneyRequestView.tsx:371-374wraps that same already-correct value inMath.abs()→ shows$1.82.The
Math.abs()is the bug.trjExpensifyconfirmed Classic shows the refund's tax as negative, and that's the semantic the rest of the codebase already assumes.Where the
Math.abscame fromCommit
23ec1dd("Add Math abs for tax formatting", 2024-06-28), part of #43519 for #42114 — QA had reported tax rendering as negative while moving a track expense into a workspace. The author's stated reason, verbatim:So it's a blanket assumption applied to fix one flow; negative expenses were never considered. The sibling
Math.absfrom that same PR was later narrowed to exactly the flow it was for —useTaxAmount.ts:83gates it onisMovingTransactionFromTrackExpense. TheMoneyRequestViewone never got that treatment.Why the negative (table) value is the correct one
getTaxAmount(transaction, true)per transaction (ReportUtils.ts:4757,:4770-4775). A refund's tax only reduces the report tax total if it is negative.:355), so you see Amount-$20.00next to Tax$1.82.What changes do you think we should make in order to solve the problem?
1. Collapse the ternary at
MoneyRequestView.tsx:371-374to one line:transactionTaxAmountis alreadygetTaxAmount()—getTransactionDetailsderives it that way (ReportUtils.ts:5022) and is called withdisplayTransaction, which already resolvesupdatedTransaction ?? onyxTransaction(:236→:318→:340). So theupdatedTransaction?.taxAmount !== undefinedbranch is redundant and wrong — it reads the raw stored (inverted-sign) value.Why dropping the
ifbranch is safe, and why keeping it would be inconsistentupdatedTransactionis only ever passed by the merge and duplicate confirmation pages (TransactionMerge/DynamicConfirmationPage.tsx:231,TransactionDuplicate/DynamicConfirmationPage.tsx:199), so the normal expense-details view always takes theelse— fixing only theelsedoes fix the reported bug.Both of those flows also store
taxAmountin the inverted convention, sogetTaxAmountis correct for them too: the duplicate page negates explicitly (:96), and the merge path computes it from the raw storedamountthrough the sign-preservingcalculateTaxAmount(MergeTransactionUtils.ts:804).Keep the ternary and fix only the
else, and merge/duplicate still renders an unsigned tax next to a signed amount — the same inconsistency, just moved.2. Do the same for the converted tax at
MoneyRequestView.tsx:376.convertedTaxAmountcarries the same inverted sign — the report tax total subtracts it (ReportUtils.ts:4774) — so itsMath.abshides the identical bug for foreign-currency expenses. There is no getter for it yet; cleanest is agetConvertedTaxAmountinTransactionUtilsmirroringgetTaxAmount, exposed throughgetTransactionDetailsalongsideconvertedAmount, rather than negating inline.What alternative solutions did you explore? (Optional)
Making
TaxCellshowMath.abs()so the table matches the details page. Rejected: it would put a positive tax next to a negative Total in the same row, and would no longer match the report's own tax total.trjExpensifyand Classic both confirm negative is the correct display.Adjacent defect found while investigating (separate from this issue)
The tax edit page renders and submits an absolute value (
DynamicIOURequestStepTaxAmountPage.tsx), which then gets negated on store — so editing a refund's tax looks like it silently flips its sign. Traced through the optimistic path only, not confirmed end-to-end against the backend response.Verification notes
Static analysis against
b060a99. I could not drive the app in a browser in this run, so the rendered strings are unconfirmed by me — but the reporters' videos and the code path agree.Note the Tax column is only rendered by the wide table row, so the mismatch is visible on desktop/wide layouts.
Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/98098)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
melvin-bot commentedon Sep 5, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @bernhardoj (
External)melvin-bot commentedon Sep 5, 2026
Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.
bernhardoj commentedon Sep 6, 2026
@jponikarchuk is this still reproducible? I can't seem to reproduce it
45 remaining items
mitarachim commentedon Oct 9, 2026
Deploy Blocker #103666 was identified to be related to the PR #102175 that attempted to fix this issue.
mitarachim commentedon Oct 10, 2026
Deploy Blocker #103671 was identified to be related to the PR #102175 that attempted to fix this issue.
bernhardoj commentedon Oct 10, 2026
Neither is a regression. I already mentioned it here. @grgia what should we do for both?
Not a regression. It's a case that's not handled in the PR. A PR is open to handle it.
This is a BE issue. Explained in #103666 (comment)
Existing hidden bug as explained by Melvin here. A PR is open to handle it.
jponikarchuk commentedon Oct 12, 2026
Deploy Blocker #103702 was identified to be related to the PR #102175 that attempted to fix this issue.
melvin-bot commentedon Oct 12, 2026
If you are the assigned CME please investigate whether the linked PR caused a regression and leave a comment with the results.
If a regression has occurred and you are the assigned CM follow the instructions here.
If this regression could have been avoided please consider also proposing a recommendation to the PR checklist so that we can avoid it in the future.
jponikarchuk commentedon Oct 12, 2026
Deploy Blocker #103707 was identified to be related to the PR #102175 that attempted to fix this issue.
bernhardoj commentedon Oct 12, 2026
An existing issue
The first real regression. The stored amount is not flipped like the normal case, so I think this is really hard to catch. This is also related Expense - Tax amount is not negative after merging expenses with negative tax amount offline and will probably be fixed by the open PR.
jponikarchuk commentedon Oct 12, 2026
Deploy Blocker #103723 was identified to be related to the PR #102175 that attempted to fix this issue.
[-][Exploratory] Web - Taxes - Inconsistent tax amount display on report with negative expenses[/-][+][Due for payment 2026-10-20] [Exploratory] Web - Taxes - Inconsistent tax amount display on report with negative expenses[/+]melvin-bot commentedon Oct 13, 2026
@bernhardoj
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.7-3 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
If no regressions arise, payment will be issued on 2026-10-20. 🎊
The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the Contributor+ Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]. If no checklist is needed for this issue, you can click: [no checklist button]