ADR 0002 — Conformité au socle de la réforme française (BT-23, BT-8)

Copy Markdown View Source

Contexte

L'ADR 0001 a retenu EN 16931 comme premier profil. Ce profil suffit au cas transfrontalier, mais il ne couvre pas tout le domestique français : la réforme impose des mentions que l'EN 16931 nu ne porte pas, dont la nature de l'opération (livraison de biens / prestation de services / mixte), qui détermine le régime d'exigibilité de la TVA.

La question posée était : « faut-il un profil EXTENDED-CTC-FR avec ses champs EXT-FR-FE-* et son propre XSD ? »

Vérification sur source primaire (spécifications externes B2B v3.2 du 30/04/2026 — méthode et références détaillées en Annexe A) : non. Le besoin se réduit à deux données déjà présentes dans le vocabulaire CII EN 16931 :

DonnéeCard.Chemin CIIRôle
BT-231..1ExchangedDocumentContext/BusinessProcessSpecifiedDocumentContextParameter/IDcadre de facturation — porte la catégorie d'opération
BT-80..1ApplicableHeaderTradeSettlement/ApplicableTradeTax/DueDateTypeCodeoption de paiement de la TVA d'après les débits

Trois constats ont dimensionné la décision :

  1. BT-23 est obligatoire (1..1), dès la trajectoire DEMARRAGE, dans les deux profils XSD du PPF (Base et Full) — et la lib ne l'émettait pas du tout. C'était le vrai manque, indépendamment de toute question de profil étendu.
  2. Aucun nouveau schéma n'est nécessaire. Les deux éléments sont déjà déclarés minOccurs="0" dans le XSD EN 16931 embarqué (BusinessProcessSpecifiedDocumentContextParameter en tête de ExchangedDocumentContextType ; DueDateTypeCode dans TradeTaxType, entre TaxPointDate et RateApplicablePercent).
  3. Le profil PPF ne se déclare pas dans BT-24. La règle S1.06 le fait porter par le préfixe du nom de fichier (Base_ / Full_). L'URN …extended-ctc-fr largement cité n'existe pas dans les spécifications.

Décision

Émettre BT-23 et BT-8, et rien d'autre pour ce jalon. Additif, non cassant.

1. Pas de profil :extended_ctc_fr

Facturx.profiles/0 est inchangé. Le profil Factur-X (qui détermine l'URN BT-24 et le ConformanceLevel XMP) et le profil PPF (Base / Full, qui détermine le jeu de contrôles applicatifs) sont deux axes orthogonaux. Introduire un atome de profil pour le second aurait mélangé les deux et exigé un URN inventé.

Corollaire opérationnel : le nommage Base_/Full_ du fichier transmis relève de l'appelant ou de sa plateforme agréée. Ce n'est pas du ressort de la lib, qui produit un document, pas un flux.

2. Codes bruts en String, pas d'atomes

business_process: "S1" plutôt que :s1 ou une paire {catégorie, variante}. Cohérent avec type_code: "380" et currency: "EUR" déjà en place. Une API sémantique (:goods / :services / :mixed) aurait mal couvert S5 (sous-traitant) et S6 (cotraitant), qui ne sont pas des catégories d'opération mais des situations de dépôt.

3. BT-8 : un champ document et un champ par entrée

Côté CII, la donnée vit dans chaque ram:ApplicableTradeTax (BG-23). La règle S1.13 impose la même valeur partout, ce qui rend un champ unique (tax_due_date_type_code) naturel pour le cas français : il est recopié sur chaque entrée, et satisfait S1.13 par construction.

Mais S1.13 est une règle française : l'EN 16931 autorise des codes différents par entrée. Un champ document-level seul obligeait donc à choisir une valeur en lisant un document tiers légitimement divergent, et un aller-retour transformait silencieusement 29, 72 en 29, 29 — un document valide au sens du schéma, mais faux sur le fait générateur de la TVA de la seconde entrée. Corrompre une donnée fiscale sans le dire est le pire des comportements possibles ici.

D'où les deux niveaux : :due_date_type_code sur une entrée de tax_breakdown l'emporte sur le champ document. Au parsing la représentation est normalisée de façon réversible — code uniforme remonté au niveau document, codes divergents conservés par entrée — ce qui préserve l'invariant d'aller-retour dans les deux cas.

Cas limite traité explicitement : un tax_due_date_type_code avec un tax_breakdown vide n'a nulle part où aller. build/2 renvoie {:error, {:vat_point_date_unemittable, code}} au lieu de le perdre en silence.

4. Une seule validation métier : la liste fermée de G1.02

C'est la seule entorse au principe de l'ADR 0001 (CII.build/2 est un sérialiseur « bête » ; la validation vit dans Facturx.XSD / Facturx.Validate, après sérialisation). Elle est assumée : le XSD ne peut pas rattraper un code BT-23 erroné, puisque DocumentContextParameterType/ID est un simple identifiant. Sans ce garde-fou, l'erreur ne se révélerait qu'au rejet par la plateforme.

Mais cette liste est française, alors que BT-23 est un business term EN 16931 dont les valeurs ne sont pas restreintes (Peppol : urn:fdc:peppol.eu:… ; Chorus Pro : A1/A2 ; autres spécifications nationales : leurs propres codes). Appliquer la contrainte française universellement enfermerait les utilisateurs non français et casserait le round-trip sur tout document tiers déjà porteur d'un BT-23 — parse/1 renseignant désormais ce champ, un parse |> build aurait échoué là où il réussissait en 0.2.0.

Le contrôle est donc opt-in : désactivé par défaut, activable une fois pour toutes via config :facturx, Facturx.CII, validate_business_process: true, ou par appel. Émettre n'est ainsi jamais bloqué par défaut, et le cas domestique obtient son garde-fou sans avoir à passer l'option partout. Le défaut « sûr » est celui qui n'enferme personne ; c'est aussi le seul qui rende cette version réellement non cassante.

En revanche la règle G1.60 (un cadre B4/S4/M4 interdit les type_code 386/500/503) n'est pas implémentée : c'est une contrainte croisée entre deux champs, qui appellerait un module de règles de gestion — un chantier distinct, à ouvrir si le besoin se confirme. La liste fermée ne vaut donc pas conformité BT-23 complète, et c'est documenté comme tel.

5. BT-8 est validé par défaut, avec échappatoire

Symétriquement, BT-8 est restreint — par l'EN 16931 elle-même (BR-CL-06) — aux trois codes 5 / 29 / 72, et l'énumération est déjà dans le dépôt (priv/schematron/en16931/FACTUR-X_EN16931_codedb.xml, liste id=28). La validation est donc universellement correcte : elle est active par défaut, à l'inverse de celle de BT-23.

Elle reste néanmoins désactivable (validate_vat_point_date: false), parce que « invalide » ne veut pas dire « qu'on ne doit jamais pouvoir le sérialiser » : le pipeline extract → parse → corriger → build sur une facture reçue est un usage central, et bloquer sans recours y serait une impasse.

Le XSD ne peut pas l'attraper (qdt:TimeReferenceCodeType est un xs:token sans énumération), et le Schematron n'est pas exécutable en CI puisqu'il exige un Saxon externe : sans ce contrôle en Elixir, une valeur erronée ne se révélerait qu'au rejet par la plateforme. Le cas est concret — les valeurs 3 / 35 / 432 appartiennent au subset UNTDID 2005 de la syntaxe UBL et sont couramment citées à tort pour du CII.

Le principe qui se dégage de ces deux décisions : on valide ce que la norme restreint, pas ce qu'un cadre national restreint — ce dernier est validé par défaut mais reste désactivable.

Conséquences

  • Facturx.Invoice gagne deux champs nil par défaut. Aucun code existant ne change de comportement ; l'usage transfrontalier n'émet ni l'un ni l'autre.
  • Facturx.CII.build/2 peut désormais renvoyer {:error, {:invalid_business_process, code}}. C'est un nouveau cas d'erreur dans un contrat qui le prévoyait déjà ({:ok, xml} | {:error, term}).
  • parse/1 relit les deux champs, ce qui préserve l'invariant de round-trip parse(build(inv)) == inv.
  • Le chemin build/2validate_xsd/2 est désormais couvert par les tests, ce qu'il n'était pas : c'est lui qui garantit que l'ordre des deux nouveaux éléments respecte les séquences du schéma. Une référence verte a été relevée sur main avant modification, afin qu'un échec ultérieur soit imputable.

Reste à faire (hors périmètre de ce jalon)

Sur les 116 données réglementaires du Flux 1, 50 sont émises. L'Annexe B détaille chaque cas ; par priorité :

  1. BT-148 (prix brut de l'article) — seul trou inconditionnel. 1..1 dans BG-29 qui est 1..1 : toute facture avec des lignes devra le porter en trajectoire CIBLE. Non bloquant au démarrage, et peu coûteux (en l'absence de rabais BT-147, le prix brut égale le prix net déjà émis).
  2. Blocs optionnels dont on voudra l'usage — chacun impose son contenu obligatoire : référence à une facture antérieure (BG-3, utile aux factures d'acompte), notes (BG-1), période de facturation (BG-14), remises et charges document (BG-20/BG-21) puis ligne (BG-27/BG-28), motif d'exonération de TVA (BT-120/BT-121), représentant fiscal (BG-11), assujetti unique (BT-29d), adresse de livraison complète (BT-76/BT-79/BT-165).
  3. Hors socle réglementaire mais fréquemment attendu : moyens de paiement / IBAN (TradeSettlementPaymentMeansType est dans le XSD embarqué, jamais émis).

Puis, si le besoin apparaît : les EXT-FR-FE-* de niveau ligne (tous en trajectoire CIBLE) et les XSD Base/Full du PPF comme cibles de validation supplémentaires — en vérifiant au préalable les conditions de redistribution de ces artefacts, priv/ étant embarqué dans le paquet Hex.