From 90135ae65a72c54a5b7b9889e8052861a0acb198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Olivi=C3=A9?= Date: Fri, 12 Jun 2026 15:08:23 +0000 Subject: [PATCH] cef: validate VATEX codes by pattern The cef-vatex catalogue hard-enumerated the official CEF VATEX codes, which left no room for the country codes that profiles admit, such as the 24 VATEX-FR-CGI* codes of the French CTC Extended profile. The catalogue now enforces the code shape only, via the pattern ^VATEX-[A-Z]{2}(-[A-Z0-9]+)+$, which covers all 75 official codes. Schematrons own the allowed list for each profile. Co-Authored-By: Claude Fable 5 Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 4 + addons/eu/en16931/tax_combo_test.go | 13 + catalogues/cef/cef_test.go | 35 +++ data/catalogues/cef.json | 453 +--------------------------- 4 files changed, 53 insertions(+), 452 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b55d22fb..8f9d287d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ## [Unreleased] +### Changed + +- `catalogues/cef`: the `cef-vatex` extension validates VATEX codes against the pattern `^VATEX-[A-Z]{2}(-[A-Z0-9]+)+$` instead of enumerating the 75 official codes, so that the country codes admitted by national profiles, such as the French `VATEX-FR-CGI*` ones, are accepted. Each profile's schematron checks the codes it allows. + ## [v0.503.0] - 2026-07-15 ### Added diff --git a/addons/eu/en16931/tax_combo_test.go b/addons/eu/en16931/tax_combo_test.go index 9acbe3e06..d1d48c895 100644 --- a/addons/eu/en16931/tax_combo_test.go +++ b/addons/eu/en16931/tax_combo_test.go @@ -140,6 +140,19 @@ func TestTaxComboValidation(t *testing.T) { assert.NoError(t, rules.Validate(c, tax.AddonContext(en16931.V2017))) }) + t.Run("country-extension vatex code passes the catalogue pattern", func(t *testing.T) { + c := &tax.Combo{ + Category: tax.CategoryVAT, + Key: tax.KeyExempt, + Ext: tax.ExtensionsOf(cbc.CodeMap{ + "cef-vatex": "VATEX-FR-CGI261-1", + }), + } + norm.Normalize(c, tax.AddonContext(en16931.V2017)) + err := rules.Validate(c, tax.AddonContext(en16931.V2017)) + assert.NoError(t, err) + }) + t.Run("reverse charge without vatex", func(t *testing.T) { c := &tax.Combo{ Category: tax.CategoryVAT, diff --git a/catalogues/cef/cef_test.go b/catalogues/cef/cef_test.go index 3c3b6abbd..435ace3b5 100644 --- a/catalogues/cef/cef_test.go +++ b/catalogues/cef/cef_test.go @@ -1,11 +1,13 @@ package cef_test import ( + "regexp" "testing" _ "github.com/invopop/gobl" "github.com/invopop/gobl/tax" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestInit(t *testing.T) { @@ -14,3 +16,36 @@ func TestInit(t *testing.T) { assert.NotNil(t, ed) assert.Equal(t, "cef-vatex", ed.Key.String()) } + +func TestVATEXPattern(t *testing.T) { + ed := tax.ExtensionForKey("cef-vatex") + require.NotNil(t, ed) + require.NotEmpty(t, ed.Pattern) + re := regexp.MustCompile(ed.Pattern) + + valid := []string{ + "VATEX-EU-79-C", + "VATEX-EU-132-1A", + "VATEX-EU-143-1FA", + "VATEX-EU-AE", + "VATEX-FR-FRANCHISE", + "VATEX-SA-34-1", + "VATEX-FR-CGI261-1", // outside the official list + } + for _, code := range valid { + assert.True(t, re.MatchString(code), code) + } + + invalid := []string{ + "EXEMPT-132", + "VATEX", + "VATEX-EU", + "VATEX-eu-132", + "vatex-EU-132", + "VATEX-E-132", + "VATEX-EU-132-", + } + for _, code := range invalid { + assert.False(t, re.MatchString(code), code) + } +} diff --git a/data/catalogues/cef.json b/data/catalogues/cef.json index 6802c4689..d6a40ad7c 100644 --- a/data/catalogues/cef.json +++ b/data/catalogues/cef.json @@ -13,458 +13,7 @@ "desc": { "en": "Codes for the reasons for VAT exemption as defined by the Connecting Europe Facility (CEF)." }, - "values": [ - { - "code": "VATEX-EU-79-C", - "name": { - "en": "Exempt based on article 79, point c of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-132", - "name": { - "en": "Exempt based on article 132 of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-132-1A", - "name": { - "en": "Exempt based on article 132, section 1 (a) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-132-1B", - "name": { - "en": "Exempt based on article 132, section 1 (b) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-132-1C", - "name": { - "en": "Exempt based on article 132, section 1 (c) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-132-1D", - "name": { - "en": "Exempt based on article 132, section 1 (d) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-132-1E", - "name": { - "en": "Exempt based on article 132, section 1 (e) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-132-1F", - "name": { - "en": "Exempt based on article 132, section 1 (f) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-132-1G", - "name": { - "en": "Exempt based on article 132, section 1 (g) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-132-1H", - "name": { - "en": "Exempt based on article 132, section 1 (h) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-132-1I", - "name": { - "en": "Exempt based on article 132, section 1 (i) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-132-1J", - "name": { - "en": "Exempt based on article 132, section 1 (j) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-132-1K", - "name": { - "en": "Exempt based on article 132, section 1 (k) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-132-1L", - "name": { - "en": "Exempt based on article 132, section 1 (l) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-132-1M", - "name": { - "en": "Exempt based on article 132, section 1 (m) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-132-1N", - "name": { - "en": "Exempt based on article 132, section 1 (n) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-132-1O", - "name": { - "en": "Exempt based on article 132, section 1 (o) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-132-1P", - "name": { - "en": "Exempt based on article 132, section 1 (p) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-132-1Q", - "name": { - "en": "Exempt based on article 132, section 1 (q) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-143", - "name": { - "en": "Exempt based on article 143 of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-143-1A", - "name": { - "en": "Exempt based on article 143, section 1 (a) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-143-1B", - "name": { - "en": "Exempt based on article 143, section 1 (b) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-143-1C", - "name": { - "en": "Exempt based on article 143, section 1 (c) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-143-1D", - "name": { - "en": "Exempt based on article 143, section 1 (d) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-143-1E", - "name": { - "en": "Exempt based on article 143, section 1 (e) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-143-1F", - "name": { - "en": "Exempt based on article 143, section 1 (f) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-143-1FA", - "name": { - "en": "Exempt based on article 143, section 1 (fa) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-143-1G", - "name": { - "en": "Exempt based on article 143, section 1 (g) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-143-1H", - "name": { - "en": "Exempt based on article 143, section 1 (h) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-143-1I", - "name": { - "en": "Exempt based on article 143, section 1 (i) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-143-1J", - "name": { - "en": "Exempt based on article 143, section 1 (j) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-143-1K", - "name": { - "en": "Exempt based on article 143, section 1 (k) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-143-1L", - "name": { - "en": "Exempt based on article 143, section 1 (l) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-148", - "name": { - "en": "Exempt based on article 148 of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-148-A", - "name": { - "en": "Exempt based on article 148, section (a) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-148-B", - "name": { - "en": "Exempt based on article 148, section (b) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-148-C", - "name": { - "en": "Exempt based on article 148, section (c) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-148-D", - "name": { - "en": "Exempt based on article 148, section (d) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-148-E", - "name": { - "en": "Exempt based on article 148, section (e) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-148-F", - "name": { - "en": "Exempt based on article 148, section (f) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-148-G", - "name": { - "en": "Exempt based on article 148, section (g) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-151", - "name": { - "en": "Exempt based on article 151 of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-151-1A", - "name": { - "en": "Exempt based on article 151, section 1 (a) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-151-1AA", - "name": { - "en": "Exempt based on article 151, section 1 (aa) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-151-1B", - "name": { - "en": "Exempt based on article 151, section 1 (b) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-151-1C", - "name": { - "en": "Exempt based on article 151, section 1 (c) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-151-1D", - "name": { - "en": "Exempt based on article 151, section 1 (d) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-151-1E", - "name": { - "en": "Exempt based on article 151, section 1 (e) of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-309", - "name": { - "en": "Exempt based on article 309 of Council Directive 2006/112/EC" - } - }, - { - "code": "VATEX-EU-AE", - "name": { - "en": "Reverse charge" - } - }, - { - "code": "VATEX-EU-D", - "name": { - "en": "Travel agents VAT scheme." - } - }, - { - "code": "VATEX-EU-F", - "name": { - "en": "Second hand goods VAT scheme." - } - }, - { - "code": "VATEX-EU-G", - "name": { - "en": "Export outside the EU" - } - }, - { - "code": "VATEX-EU-I", - "name": { - "en": "Works of art VAT scheme." - } - }, - { - "code": "VATEX-EU-IC", - "name": { - "en": "Intra-community supply" - } - }, - { - "code": "VATEX-EU-J", - "name": { - "en": "Collectors items and antiques VAT scheme." - } - }, - { - "code": "VATEX-EU-O", - "name": { - "en": "Not subject to VAT" - } - }, - { - "code": "VATEX-FR-FRANCHISE", - "name": { - "en": "France domestic VAT franchise in base" - } - }, - { - "code": "VATEX-FR-CNWVAT", - "name": { - "en": "France domestic Credit Notes without VAT, due to supplier forfeit of VAT for discount" - } - }, - { - "code": "VATEX-SA-29", - "name": { - "en": "Financial services mentioned in Article 29 of the VAT Regulations" - } - }, - { - "code": "VATEX-SA-29-7", - "name": { - "en": "Life insurance services mentioned in Article 29 of the VAT Regulations" - } - }, - { - "code": "VATEX-SA-30", - "name": { - "en": "Real estate transactions mentioned in Article 30 of the VAT Regulations" - } - }, - { - "code": "VATEX-SA-32", - "name": { - "en": "Export of goods" - } - }, - { - "code": "VATEX-SA-33", - "name": { - "en": "Export of services" - } - }, - { - "code": "VATEX-SA-34-1", - "name": { - "en": "The international transport of goods" - } - }, - { - "code": "VATEX-SA-34-2", - "name": { - "en": "International transport of passengers" - } - }, - { - "code": "VATEX-SA-34-3", - "name": { - "en": "Services directly connected and incidental to a supply of international passenger transport" - } - }, - { - "code": "VATEX-SA-34-4", - "name": { - "en": "Supply of a qualifying means of transport" - } - }, - { - "code": "VATEX-SA-34-5", - "name": { - "en": "Any services relating to goods or passenger transportation, as defined in Article 25 of the VAT Regulations" - } - }, - { - "code": "VATEX-SA-35", - "name": { - "en": "Medicines and medical equipment" - } - }, - { - "code": "VATEX-SA-36", - "name": { - "en": "Qualifying metals" - } - }, - { - "code": "VATEX-SA-EDU", - "name": { - "en": "Private education to citizen" - } - }, - { - "code": "VATEX-SA-HEA", - "name": { - "en": "Private healthcare to citizen" - } - }, - { - "code": "VATEX-SA-MLTRY", - "name": { - "en": "Supply of qualified military goods" - } - }, - { - "code": "VATEX-SA-OOS", - "name": { - "en": "Reason is free text, to be provided by the taxpayer on a case-by-case basis" - } - } - ] + "pattern": "^VATEX-[A-Z]{2}(-[A-Z0-9]+)+$" } ] } \ No newline at end of file