Skip to content

Switch to precise rounding when removing included taxes - #923

Open
samlown wants to merge 1 commit into
mainfrom
fix-remove-included-taxes-currency-rounding
Open

Switch to precise rounding when removing included taxes#923
samlown wants to merge 1 commit into
mainfrom
fix-remove-included-taxes-currency-rounding

Conversation

@samlown

@samlown samlown commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

When an invoice combines tax.prices_include: VAT with tax.rounding: currency, RemoveIncludedTaxes produced a tax breakdown that no longer matched the original document.

The problem

In currency mode, extractIncludedTaxes derives the tax from the running sum of the tax-inclusive line totals and shares the rounding back over the lines, so the ex-tax base it reports is not the sum of independently-rounded per-line ex-tax amounts.

The convertor discarded that: it divides each unit price by the rate (+2 decimal places), and calculateLine then re-rounds each line total down to the currency's precision. For identical lines the per-line loss has the same sign every time, so it accumulates:

Case Original base / tax After removal totals.rounding
1 × 100.00 @ 21% 82.64 / 17.36 82.64 / 17.35 0.01
12 × 125.00 @ 6% 1415.09 / 84.91 1415.04 / 84.90 0.06
30 × 125.00 @ 6% 3537.74 / 212.26 3537.60 / 212.26 0.14
50 × 19.99 @ 19% 839.92 / 159.58 840.00 / 159.60 −0.10

Payable stayed correct because the residual landed in Totals.Rounding (BT-144), but the tax breakdown itself — the part that gets reported — was wrong, and the error grew with the number of lines rather than staying within the "cent or two" the method documents.

The fix

removeIncludedTaxes switches a currency-rounded document to precise, resolving the regime default first so the rule is explicit in the output.

Currency rounding's contract is that every amount is rounded to the currency before summing. Once included taxes are removed the unit price carries extra decimal places, so that contract is already broken and cannot be restored. Precise mode keeps the line total at the price's precision and rounds once at the base, which is arithmetically the same operation as Percent.From(inclusive_sum). All four cases above then reproduce the original base and tax exactly, with no rounding adjustment at all.

Alternative considered

Making currency mode respect the item price's precision when it exceeds the currency's also fixes the totals, but examples/de/invoice-de-rounding-2.json pins the opposite requirement: 4-decimal input prices under currency rounding must yield 2-decimal line totals, as XRechnung/ZUGFeRD require. That approach was dropped.

Trade-off

Converted documents now carry line totals with more decimal places than the currency. If a downstream format requires 2-decimal line amounts this matters, though the alternative was an incorrect tax breakdown plus a rounding line, which those formats also reject. The switch is a single condition and easy to gate at the addon or caller level if that turns out to be preferable.

Test

TestRemoveIncludedTaxes/with currency rounding rule covers the 12 × 125.00 @ 6% case; verified failing without the fix (base 1415.04 vs 1415.09, tax 84.90 vs 84.91). Full suite passes.

🤖 Generated with Claude Code

Removing taxes included in prices from a document using the currency
rounding rule now switches it to precise, so the resulting tax bases and
amounts match those of the original document.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.95%. Comparing base (0b22571) to head (32fc9a1).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #923   +/-   ##
=======================================
  Coverage   94.95%   94.95%           
=======================================
  Files         327      327           
  Lines       17668    17673    +5     
=======================================
+ Hits        16777    16782    +5     
  Misses        549      549           
  Partials      342      342           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@samlown
samlown requested a review from cavalle August 7, 2026 14:18
@samlown

samlown commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

@cavalle Claude came up with this solution for the calculation of prices that include tax in formats that don't support it. I think its reasonable to switch to precise calculations.

@cavalle cavalle left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! This makes sense! LGTM!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants