Skip to content

[BR-O-05] When VAT category code is "O", invoice lines should not contain RateApplicablePercent#165

Open
Betelgeuse1 wants to merge 3 commits into
speedata:mainfrom
Freelance-launchpad:writer/br-o-5
Open

[BR-O-05] When VAT category code is "O", invoice lines should not contain RateApplicablePercent#165
Betelgeuse1 wants to merge 3 commits into
speedata:mainfrom
Freelance-launchpad:writer/br-o-5

Conversation

@Betelgeuse1

Copy link
Copy Markdown
Contributor

The FNFE validator enforces the rule BR-O-5, which stipulates :

An Invoice line (BG-25) where the VAT category code (BT-151) is "Not subject to VAT" shall not contain an Invoiced item VAT rate (BT-152).

We could also remove RateApplicablePercent from the document level ApplicableTradeTax.
However, the BR-DE-14 says :

Header vat breakdown (ApplicableTradeTax) must contain RateApplicablePercent (BT-119) even for category O.

So for the best of both world, I only removed RateApplicablePercent at the invoice lines level.

@pgundlach

Copy link
Copy Markdown
Member

One thing missing though: the same change needs to be applied to the UBL writer. writer_ubl.go still writes cbc:Percent unconditionally:

// writer_ubl.go:736
taxCat.CreateElement("cbc:Percent").SetText(formatPercent(line.TaxRateApplicablePercent))

For a category "O" line this emits <cbc:Percent>0</cbc:Percent>, which is exactly the BR-O-5 violation we're now enforcing on the parse side (you already updated parser_ubl.go for it). So the fix is currently asymmetric — CII output is correct, but UBL output still produces invalid invoices.

Suggested change, mirroring the CII writer:

taxCat.CreateElement("cbc:ID").SetText(line.TaxCategoryCode)
if line.TaxCategoryCode != "O" {
    taxCat.CreateElement("cbc:Percent").SetText(formatPercent(line.TaxRateApplicablePercent))
}

Would also be good to add a write/round-trip test for both CII and UBL asserting no rate is emitted for category O. That would have caught the UBL gap.

@Betelgeuse1

Copy link
Copy Markdown
Contributor Author

I've fixed the UBL writing issue and added a condition in both UBL and CII round trip test.

@pgundlach

Copy link
Copy Markdown
Member

Thank you very much for your patience. I have to admit that I was lazy this time (very busy), so I asked the AI for help, and this is the finding. I cannot judge right now if this is correct or not, please bear with me if this is nonsense:


One blocker before this can be merged, though: the UBL writer change breaks the XSD element order.

In writer_ubl.go, cbc:Percent was moved after cac:TaxScheme:

taxCat.CreateElement("cbc:ID").SetText(line.TaxCategoryCode)
taxCat.CreateElement("cac:TaxScheme").CreateElement("cbc:ID").SetText(line.TaxTypeCode)
if line.TaxCategoryCode != "O" {
    taxCat.CreateElement("cbc:Percent").SetText(formatPercent(line.TaxRateApplicablePercent))
}

cac:ClassifiedTaxCategory (UBL TaxCategoryType) has a fixed sequence: cbc:ID, cbc:Name, cbc:Percent, …, cac:TaxScheme — so cbc:Percent must come before cac:TaxScheme. As written, this produces XSD-invalid UBL for
every non-"O" line (i.e. the normal case). The round-trip test doesn't catch it because the parser is order-independent.

Could you keep the original order and just wrap the condition around the Percent element?

taxCat.CreateElement("cbc:ID").SetText(line.TaxCategoryCode)
if line.TaxCategoryCode != "O" {
    taxCat.CreateElement("cbc:Percent").SetText(formatPercent(line.TaxRateApplicablePercent))
}
taxCat.CreateElement("cac:TaxScheme").CreateElement("cbc:ID").SetText(line.TaxTypeCode)

The CII writer is fine as-is, since RateApplicablePercent sits at the end of that sequence anyway.

A small follow-up suggestion: a UBL test that asserts the actual element order in the generated XML (rather than just round-tripping) would catch this class of issue going forward.

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