fix: reconcile PEPPOL monetary totals on sub-cent invoices (BR-CO-10/15/16)#47
Open
maasanto wants to merge 4 commits into
Open
fix: reconcile PEPPOL monetary totals on sub-cent invoices (BR-CO-10/15/16)#47maasanto wants to merge 4 commits into
maasanto wants to merge 4 commits into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
A Sales Invoice whose amounts carry three decimals (e.g. a 607.025 line at 21% VAT) fails Peppol/EN16931 validation on submit, because each monetary field is rounded to 2 decimals on its own and the rounded parts no longer reconcile:
LineExtensionAmount(BT-131) usedf"{x:.2f}", which bypasses frappefltand can round the raw float differently from the flt-rounded documentLineExtensionAmount(BT-106), so the two disagree on a single sub-cent line (BR-CO-10);BT-106 = Σ BT-131breaks even after both sides useflt(BR-CO-10);TaxInclusiveAmount(BT-112) was taken straight fromgrand_total, butTaxExclusive + VAT, each rounded on its own, lands on a different cent (607.03 + 127.48 = 734.51 ≠ 734.50) (BR-CO-15).EN16931 forces this tension: BR-DEC-09/23 cap BT-106 and BT-131 at 2 decimals (a 3-decimal amount is illegal in the XML), while BR-CO-10 requires the rounded values to still add up exactly.
Fix
fltto 2 decimals and the ≤1-cent-per-line rounding residual is distributed with the largest-remainder method, soΣ BT-131 = BT-106holds for any number of lines at any precision (BR-CO-10). Each line moves by at most a cent — well inside PEPPOL-EN16931-R120's ±0.02 quantity×price tolerance — and the document totals stay authoritative (BT-109/BT-110/BT-112 are left untouched);TaxInclusiveAmountasTaxExclusive + total VATinstead of readinggrand_total, so it reconciles by construction (BR-CO-15);PayableRoundingAmount(BT-114) for the ≤1-cent residual, keepingPayableAmountequal to the real amount due (BR-CO-16). Prepaid is measured againstrounded_totalso partially-paid invoices with a rounding adjustment also balance.Plain 2-decimal invoices are unaffected: same totals, no line redistribution, no rounding line emitted.
Tests
test_generator.pydrives_set_totalsacross the sub-cent case, rounded-up/down totals, partially-paid, fully-paid, the disabled-round-off fallback, credit notes, and UBL element ordering, asserting the BR-CO-15/16 identities each time. A second suite drives the per-line reconciliation across multi-line sub-cent invoices (two/three lines, mixed magnitudes, a credit note, a deduction line), assertingΣ BT-131 = BT-106and that every line stays within R120's tolerance.Validated end-to-end on a sandbox as well: invoices generated for all these scenarios at 3-decimal currency precision pass the bundled CEN-EN16931 + PEPPOL-EN16931 Schematron with no rounding or decimal-place violations.