Skip to content

Route es-tbai-exemption=S2 to Sujeta/NoExenta. Part of APP-516#41

Merged
pmenendz merged 2 commits into
mainfrom
fix-tbai-exemption-s2
May 18, 2026
Merged

Route es-tbai-exemption=S2 to Sujeta/NoExenta. Part of APP-516#41
pmenendz merged 2 commits into
mainfrom
fix-tbai-exemption-s2

Conversation

@pmenendz

@pmenendz pmenendz commented May 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • A client reported that es-tbai-exemption=S2 did not work while the other values did. After addon normalization, an S2 rate has no Percent, and the converter routed any no-percent rate that wasn't OT/RL into Sujeta.Exenta with CausaExencion=S2 — invalid per the TBAI XSD, where CausaExencion only accepts E1–E6.
  • Make the breakdown dispatcher consult the es-tbai-exemption extension set by the es-tbai-v1 addon: S2 lands in Sujeta.NoExenta with TipoNoExenta=S2 and a zero-amount DetalleIVA; VT/IE join OT/RL in NoSujeta. The legacy tag-based reverse-charge path is preserved for callers that haven't run the addon.

Test plan

  • go test ./convert/... — new tests cover Key=tax.KeyReverseCharge, manually-set es-tbai-exemption=S2, and VT/IE routing via Key=tax.KeyOutsideScope.
  • New test/data/invoice-es-nl-reverse-charge.json fixture round-trips through the existing example pipeline; output XML emits <TipoNoExenta>S2</TipoNoExenta> with a 0.00 rate/cuota.
  • Manual: submit a reverse-charge invoice to a TBAI sandbox endpoint and confirm acceptance.

🤖 Generated with Claude Code

@pmenendz pmenendz requested review from cavalle and Copilot and removed request for cavalle May 14, 2026 10:44

Copilot AI 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.

Pull request overview

Routes es-tbai-exemption=S2 correctly to Sujeta.NoExenta with TipoNoExenta=S2 (rather than incorrectly emitting it under Sujeta.Exenta with an invalid CausaExencion=S2), and also routes VT/IE exemption codes to NoSujeta. The previous tag-based reverse-charge path is preserved as a fallback for non-normalized callers.

Changes:

  • nonExemptedType now consults the per-rate es-tbai-exemption extension (S2 → TipoNoExenta=S2) in addition to the legacy invoice-wide reverse-charge tag.
  • Expanded notSubjectExemptionCodes to include VT and IE; introduced reverseChargeExemptionCodes and excluded those codes from isExenta. newDetalleIVA now tolerates rate.Percent == nil by emitting 0.00.
  • Added unit tests and a round-trip JSON/XML fixture covering the new routing.

Reviewed changes

Copilot reviewed 3 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
convert/breakdown.go Dispatcher updates: S2 → NoExenta, VT/IE → NoSujeta, nil-percent handling.
convert/breakdown_test.go Tests for KeyReverseCharge, S2 via extension, and VT/IE outside-scope routing.
test/data/invoice-es-nl-reverse-charge.json New input fixture for an EU reverse-charge invoice.
test/data/out/invoice-es-nl-reverse-charge.xml Expected XML output verifying TipoNoExenta=S2 with zero base/cuota.
.gitignore Ignore /bin/.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

The converter previously mapped any rate without a percent to either
NoSujeta (for OT/RL) or Exenta (everything else), so reverse-charge
combos (es-tbai-exemption=S2, percent nil after addon normalization)
ended up under CausaExencion=S2, which is invalid per the TBAI XSD.

Extend the dispatcher to read es-tbai-exemption set by the es-tbai-v1
addon: S2 routes to Sujeta/NoExenta/TipoNoExenta=S2 with a 0% DetalleIVA,
and VT/IE join OT/RL in NoSujeta. The tag-based reverse-charge fallback
is preserved for invoices built without running the addon.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pmenendz pmenendz force-pushed the fix-tbai-exemption-s2 branch from b6e9e33 to 1161c55 Compare May 14, 2026 11:06
pmenendz added a commit that referenced this pull request May 14, 2026
The S2/reverse-charge dispatcher changes in convert/breakdown.go and
the invoice-es-es-reverse-charge fixture are owned by PR #41
(fix-tbai-exemption-s2). Removing them here so this PR's diff only
shows the es-tbai-regime + es-tbai-identity-type work.

@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.

Approving to keep the flow but please review and consider the comments

Comment thread convert/breakdown.go Outdated
Comment thread convert/breakdown.go Outdated
Comment thread .gitignore Outdated
Gate Convert on the addon being declared and remove the
TagReverseCharge fallback in the breakdown, since the addon
normalizer now guarantees es-tbai-exemption is set on every combo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pmenendz pmenendz force-pushed the fix-tbai-exemption-s2 branch from 80b9e31 to 912b77b Compare May 18, 2026 15:09
@pmenendz pmenendz changed the title Route es-tbai-exemption=S2 to Sujeta/NoExenta Route es-tbai-exemption=S2 to Sujeta/NoExenta. Part of APP-516 May 18, 2026
@pmenendz pmenendz merged commit 930f124 into main May 18, 2026
5 checks passed
@pmenendz pmenendz deleted the fix-tbai-exemption-s2 branch May 18, 2026 15:18
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.

3 participants