Access.It.Pdf.Facturx 10.6.5

dotnet add package Access.It.Pdf.Facturx --version 10.6.5
                    
NuGet\Install-Package Access.It.Pdf.Facturx -Version 10.6.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Access.It.Pdf.Facturx" Version="10.6.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Access.It.Pdf.Facturx" Version="10.6.5" />
                    
Directory.Packages.props
<PackageReference Include="Access.It.Pdf.Facturx" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Access.It.Pdf.Facturx --version 10.6.5
                    
#r "nuget: Access.It.Pdf.Facturx, 10.6.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Access.It.Pdf.Facturx@10.6.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Access.It.Pdf.Facturx&version=10.6.5
                    
Install as a Cake Addin
#tool nuget:?package=Access.It.Pdf.Facturx&version=10.6.5
                    
Install as a Cake Tool

Access.It.Pdf.Facturx

Génération ET lecture de factures Factur-X (EN 16931) pour .NET 8 et .NET 10.

Fournit :

  • API duale byte[] + Stream pour Fusion / Importer / PdfaPreCheck — RAM-efficient pour services web et gros PDFs
  • Modèles typés BT/BG EN 16931 (records C# immuables)
  • Auto-détection du profil minimal (FacturXProfileResolver.DetectMinimal) — évite d'envoyer EN16931 quand Basic ou Minimum suffit
  • Validateur modèle pré-XML (IFacturXModelValidator) : champs requis + cohérence totaux (BR-CO-10/13/14/16) avant sérialisation — feedback immédiat
  • Helpers de calcul métier (FacturXCalculator) : ComputeVatBreakdown + ComputeTotals — bâtit les ventilations et totaux à partir des lignes, garantit cohérence BR-CO-*
  • Générateur XML CII UN/CEFACT D16B (XSD-conforme, ordre des éléments strict)
  • Validateur structurel des règles BR-* critiques (~20 sur 138)
  • Validateur XSD officiel par profil (UN/CEFACT CII embarqués : Minimum / Basic / EN 16931 / Extended)
  • Pre-check PDF/A-3 du PDF source (IFacturXPdfaCompliancePreCheck) : détecte fonts non embedded, CIDToGIDMap manquant, transparence avant fusion
  • Fusion PDF/A-3 via PdfSharp 7 (OutputIntent sRGB, XMP Factur-X, embedded file, AF)
  • Importer : extraction du XML CII embarqué d'un Factur-X existant (use case réception facture)
  • DI extension : services.AddAccessItFacturx()

Aucune dépendance commerciale (Aspose, iText AGPL) — PdfSharp MIT uniquement.

Installation

<PackageReference Include="Access.It.Pdf.Facturx" />

Puis dans Program.cs :

services.AddAccessItFacturx();

Usage

using Access.It.Facturx.Models;
using Access.It.Facturx.Models.Enums;
using Access.It.Facturx.Services.Abstract;

public class InvoiceFacturXBuilder(
    IFacturXXmlGenerator generator,
    IFacturXValidator validator,
    IFacturXFusion fusion)
{
    public async Task<byte[]> BuildAsync(byte[] sourcePdf, MyInvoice invoice, CancellationToken ct)
    {
        // 1. Construire le modèle Factur-X depuis vos entités métier
        var model = new FacturXInvoiceModel
        {
            Number = invoice.Number,
            IssueDate = invoice.Date,
            TypeCode = DocumentTypeCode.CommercialInvoice,
            BusinessProcess = BusinessProcessCode.ServicesInvoice,
            Seller = new FacturXSellerModel { /* ... */ },
            Buyer = new FacturXBuyerModel { /* ... */ },
            Lines = invoice.Lines.Select(MapLine).ToList(),
            VatBreakdown = ComputeVatBreakdown(invoice),
            Totals = ComputeTotals(invoice)
        };

        // 2. Générer l'XML CII
        var xml = generator.Generate(model, FacturXProfile.EN16931);

        // 3. (Optionnel) Valider la structure XSD avant fusion
        var report = await validator.ValidateAsync(xml, ct);
        if (!report.IsValid)
            throw new InvalidOperationException(
                $"Factur-X invalid: {string.Join("; ", report.Errors.Select(e => e.Message))}");

        // 4. Fusionner XML + PDF source → PDF/A-3 Factur-X
        return await fusion.EmbedAsync(sourcePdf, xml, FacturXProfile.EN16931, ct);
    }
}

Workflow RTF / Word / Excel → Factur-X

Cette lib ne gère que le PDF en entrée. La conversion d'un document source (RTF, DOCX, XLSX, HTML, etc.) vers PDF est volontairement déléguée à d'autres libs dédiées du mono-repo AIT, pour garder Access.It.Pdf.Facturx en pur MIT (sans dépendance commerciale Aspose).

Combine avec :

  • Access.It.Pdf.WordToPdf — Word / RTF → PDF (via Aspose.Words)
  • Access.It.Pdf.ExcelToPdf — Excel → PDF (via Aspose.Cells)

Pattern recommandé pour un workflow RTF → Factur-X :

public class RtfToFacturX(
    IWordToPdfConverter wordConverter,    // Access.It.Pdf.WordToPdf
    IFacturXXmlGenerator generator,        // Access.It.Pdf.Facturx
    IFacturXFusion fusion)                 // Access.It.Pdf.Facturx
{
    public async Task<byte[]> ConvertAsync(byte[] rtfBytes, FacturXInvoiceModel invoice, CancellationToken ct)
    {
        var pdfBytes = await wordConverter.ConvertRtfToPdfAsync(rtfBytes, ct);
        var xml = generator.Generate(invoice);
        return await fusion.EmbedAsync(pdfBytes, xml, FacturXProfile.EN16931, ct);
    }
}

Calcul + validation pré-XML (FacturXCalculator + IFacturXModelValidator)

Plutôt que de calculer la VAT breakdown et les totaux à la main côté caller (source de bugs d'arrondi BR-CO-10/13/14/16), on bâtit le modèle à partir des lignes seules :

using Access.It.Facturx.Services;
using Access.It.Facturx.Services.Abstract;

var lines = invoice.Lines.Select(MapLine).ToList();
var breakdown = FacturXCalculator.ComputeVatBreakdown(lines, documentAllowances: null);
var totals = FacturXCalculator.ComputeTotals(lines, breakdown);

var model = new FacturXInvoiceModel
{
    /* en-tête + Seller + Buyer */
    Lines = lines,
    VatBreakdown = breakdown,
    Totals = totals
};

// Pré-validation rapide (in-memory, pas d'XML) : champs requis + cohérence totaux
var preReport = modelValidator.Validate(model);
if (!preReport.IsValid)
    throw new InvalidOperationException(string.Join("; ", preReport.Errors.Select(e => e.Message)));

var xml = generator.Generate(model);

Le contrat est : la sortie de FacturXCalculator satisfait toujours IFacturXModelValidator (testé). Utiliser les deux ensemble élimine la classe de bugs « total différent de la somme des lignes ».

Arrondi : 2 décimales, MidpointRounding.AwayFromZero (arrondi commercial français).

Auto-détection du profil (FacturXProfileResolver)

Plutôt que de hard-coder EN16931 partout, demander à la lib le profil le plus simple compatible avec un modèle donné :

using Access.It.Facturx.Services;

var profile = FacturXProfileResolver.DetectMinimal(model);
var xml = generator.Generate(model, profile);
var pdf = await fusion.EmbedAsync(sourcePdf, xml, profile);

Heuristique :

  • Aucune ligne → Minimum
  • Lignes simples (TVA standard S uniquement, pas d'allowance/charge/exemption, 1 seule TVA, pas de payment means, pas de période, pas de ShipTo, pas de précédente facture) → Basic
  • Toute feature avancée (autoliquidation, exemption, document allowance, multi-TVA, ShipTo, payment means, période, credit note référençant une facture) → EN16931

Extended n'est pas détecté automatiquement (aucun champ Extended-only n'est exposé par le modèle actuel — le caller doit le sélectionner explicitement si besoin).

Adresse de routage française (FrCtcElectronicAddressBuilder)

Le PPF ne route pas sur le SIRET brut : la valeur du <URIID> (BT-34 Seller / BT-49 Buyer) se compose à partir du SIREN, sous le schemeID 0225.

using Access.It.Facturx.Models.Enums;
using Access.It.Facturx.Services;

var buyer = new FacturXBuyerModel
{
    // ...
    ElectronicAddress = FrCtcElectronicAddressBuilder.TryBuild(
        "33514718700116", FrCtcAddressingMode.SiretWithServiceCode, "COMPTA")
    // → Value = "335147187_33514718700116_COMPTA", SchemeId = "0225"
};
FrCtcAddressingMode Valeur produite
Siren SIREN
Siret SIREN_SIRET
SirenWithServiceCode SIREN_codeService
SiretWithServiceCode SIREN_SIRET_codeService

Le formatage de l'identifiant est ignoré (335 147 187 00116 est accepté). TryBuild renvoie null si l'identifiant ne porte pas de SIREN exploitable — moins de 9 chiffres, ou SIREN entièrement à zéro. Quand un code service est attendu mais absent, le mode est dégradé vers sa variante sans code service plutôt que de produire une adresse tronquée.

TryBuildValue renvoie la seule chaîne, pour composer une adresse dérivée.

Attention : se tromper ici ne déclenche aucune erreur de validation. L'XML reste conforme et la fusion réussit, mais la facture n'atteint pas son destinataire.

Régime de TVA français (FrVatRegimeResolver)

Détermine la catégorie de TVA (BT-118 / BT-151), le code d'exonération (BT-121) et le motif littéral (BT-120) pour un vendeur français.

var regime = FrVatRegimeResolver.Resolve(
    buyerCountryCode: "BE",
    buyerIsEuropean: true,
    businessProcess: BusinessProcessCode.ServicesInvoice);
// → AE / VATEX_EU_AE / "Autoliquidation - Service intracommunautaire UE - Art. 259B CGI"
Situation Catégorie Code Fondement
Acheteur hors UE G VATEX_EU_G Art. 262 I CGI
Acheteur UE, services AE VATEX_EU_AE Art. 259B CGI
Acheteur UE, biens AE VATEX_EU_IC Art. 262 ter I CGI
FR, métaux et ferrailles AE VATEX_FR_AE Art. 283-2 sexies CGI
FR, franchise en base E VATEX_FR_FRANCHISE Art. 293 B CGI
FR, navire maritime E VATEX_EU_148 Art. 262 II CGI
FR, cas général S

La géographie de l'acheteur prime sur les exonérations de droit interne : un acheteur hors de France ne peut pas relever d'une franchise française. Un code pays null ou vide est traité comme la France. Le numéro IMO, s'il est fourni, est ajouté au motif du navire.

Lignes d'adresse (FacturXAddressLineBuilder)

EN 16931 n'accepte que trois lignes d'adresse de 50 caractères (BT-35 / BT-36 / BT-37).

var (one, two, three) = FacturXAddressLineBuilder.Split(
    "Zone industrielle de la Pilaterie", "rue des Chateaux", "CS 53041");

La coupe se fait sur les espaces pour ne pas casser un mot. Un mot plus long que la limite est tronçonné, faute d'alternative. Ce qui dépasse trois lignes est abandonné : une quatrième ligne ferait échouer la validation XSD plutôt qu'enrichir l'adresse. Les fragments null ou blancs sont ignorés, on peut donc passer des champs optionnels tels quels.

Numéro de TVA intracommunautaire (FacturXVatIdentifier)

if (!FacturXVatIdentifier.HasCountryPrefix(vatId))
    throw new InvalidOperationException($"BT-31 : préfixe pays manquant sur '{vatId}'.");

EN 16931 impose le préfixe pays sur deux lettres. Un numéro français saisi sans son FR passe l'XSD, qui ne voit qu'une chaîne, mais sera rejeté à la réception. Le contrôle porte sur la seule forme — la validité réelle du numéro relève de VIES.

API RAM-efficient (Stream)

IFacturXFusion, IFacturXImporter et IFacturXPdfaCompliancePreCheck exposent chacun deux overloads :

// byte[] : simple, idéal pour des PDFs courts et un usage ponctuel
Task<byte[]> EmbedAsync(byte[] pdfBytes, string xml, FacturXProfile profile, CancellationToken ct);

// Stream : RAM-efficient, idéal pour services web et batches
Task EmbedAsync(Stream input, Stream output, string xml, FacturXProfile profile, CancellationToken ct);

Le caller peut alors streamer directement depuis un HttpRequest.Body ou un FileStream vers la sortie sans tout charger en mémoire :

// Endpoint ASP.NET Core - peak RAM ~taille du PDF (vs ~3x en byte[])
[HttpPost("/api/invoices/facturx")]
public async Task EmbedAsync(IFacturXFusion fusion, HttpContext ctx)
{
    ctx.Response.ContentType = "application/pdf";
    await fusion.EmbedAsync(ctx.Request.Body, ctx.Response.Body, xml, profile, ctx.RequestAborted);
}

Les streams fournis ne sont pas disposés par la lib (responsabilité du caller). Si l'input est non-seekable (HTTP body, NetworkStream), un buffer interne MemoryStream est utilisé (limitation PdfSharp qui exige seekable). Pour les inputs seekable (FileStream, MemoryStream), aucune allocation supplémentaire.

Validation XSD officielle (par profil)

IFacturXXsdValidator valide l'XML contre le schéma XSD officiel UN/CEFACT correspondant au profil cible. Plus strict que le validateur structurel (vérifie types, multiplicités, ordre, attributs) — équivalent de la validation officielle des PDPs.

using Access.It.Facturx.Services.Abstract;
using Access.It.Facturx.Models.Enums;

public class StrictFacturXValidator(IFacturXXsdValidator xsdValidator)
{
    public async Task EnsureValidAsync(string xml, FacturXProfile profile, CancellationToken ct)
    {
        var report = await xsdValidator.ValidateAsync(xml, profile, ct);
        if (!report.IsValid)
            throw new InvalidOperationException(
                $"Factur-X XSD invalide pour profil {profile} : {string.Join("; ", report.Errors.Select(e => e.Message))}");
    }
}

Les 4 fichiers XSD officiels par profil (root + QualifiedDataType + ReusableAggregate + UnqualifiedDataType) sont embarqués comme ressources. Le XmlSchemaSet est mis en cache par profil (build coûteux mais réutilisable).

Lecture d'un Factur-X reçu

Use case côté consommateur : tu reçois une facture Factur-X via ta PDP / email, tu veux extraire l'XML structuré pour l'intégrer automatiquement dans ta compta.

using Access.It.Facturx.Services.Abstract;

public class IncomingInvoiceParser(IFacturXImporter importer)
{
    public async Task<string?> ExtractInvoiceDataAsync(byte[] receivedPdfBytes, CancellationToken ct)
    {
        // Returns the CII XML string, or null if the PDF is not a Factur-X
        return await importer.ReadEmbeddedXmlAsync(receivedPdfBytes, ct);
    }
}

Stratégie de lookup (premier match) :

  1. Entrée nommée factur-x.xml dans /Catalog/Names/EmbeddedFiles
  2. Tout fichier embarqué dont le Subtype MIME est text/xml ou application/xml ET qui contient CrossIndustryInvoice (filtrage par contenu)

Le roundtrip Fusion → Importer est garanti byte-exact (testé).

Profils Factur-X supportés

Profil Description
Minimum Profil minimal (totaux + identité)
Basic Données structurées de base
EN16931 Profil cible légal européen complet (défaut)
Extended EN 16931 + champs métier supplémentaires

Couverture XSD / BR-*

Conformité XSD CII garantie par construction

  • Ordre des éléments SupplyChainTradeTransaction : lignes avant blocs header
  • BusinessProcessSpecifiedDocumentContextParameter (suffixe Parameter obligatoire)
  • TradeParty : IDNameSpecifiedLegalOrganizationPostalTradeAddressURIUniversalCommunicationSpecifiedTaxRegistration
  • PostalTradeAddress : PostcodeCodeLineOneLineTwoLineThreeCityNameCountryID
  • BR-O-02 : VAT IDs omis si une ligne utilise catégorie O

Validation runtime par FacturXValidator (~20 BR critiques)

  • BG-2, BG-4, BG-22, BG-23, BG-25 (blocs obligatoires)
  • BT-1, BT-2, BT-3, BT-5, BT-24, BT-27, BT-40, BT-44, BT-55 (champs en-tête)
  • BR-5, BR-6, BR-7, BR-9, BR-11 (validations Seller/Buyer/Currency)
  • BR-12 à BR-16, BR-CO-15, BR-CO-25 (totaux + lignes)

Conformité PDF/A-3 (fusion)

  • pdfaid:part=3, pdfaid:conformance=B (XMP)
  • fx:DocumentType=INVOICE, fx:DocumentFileName, fx:Version, fx:ConformanceLevel (XMP Factur-X)
  • pdfaExtension:schemas (déclaration namespace fx:)
  • /Catalog/AF (Associated Files PDF/A-3)
  • /Catalog/Names/EmbeddedFiles (name tree avec factur-x.xml)
  • /Catalog/OutputIntents avec profil sRGB IEC61966-2.1 ICC (GTS_PDFA1)
  • /Filespec avec /Subtype=/text/xml + /AFRelationship=/Alternative
  • /EmbeddedFile stream avec MIME text/xml

Limites connues

Matrice de conformité EN 16931 détaillée

Une matrice par règle BR-* (avec mécanisme de couverture par règle, statut détaillé, gap analysis) est disponible dans EN16931-COVERAGE.md (à la racine du package). Synthèse : ~40-45% des 138 BR-* Schematron couvertes structurellement, ~90% des règles qui causent un rejet PDP en pratique (totaux BR-CO + codelist BR-CL + XSD + PDF/A).

Validation Schematron complète

La validation des 138 règles BR-* EN 16931 nécessite un moteur XSLT 2.0 :

  • Saxon-HE NuGet est .NET Framework only
  • SaxonCS .NET est commercial

Le Schematron officiel EN16931-CII-validation.xslt est embarqué comme ressource pour exécution par un sidecar Java/Saxon ou un job CI saxonche.

FacturXValidator couvre uniquement la validation structurelle critique (~20 BR-*).

Conformité PDF/A-3 stricte du PDF source

La fusion ajoute tous les marqueurs Factur-X requis, mais la conformité PDF/A-3 stricte dépend du PDF source :

  • Fonts embedded (pas Helvetica built-in)
  • Pas de glyphs .notdef (caractères absents du font)
  • CIDToGIDMap sur les CIDFonts
  • Pas de Transparency groupe sur les pages

Ces erreurs ISO 19005-1 sont à fixer côté générateur du PDF source (template Aspose / iText / Word / Carbone / etc.).

IFacturXPdfaCompliancePreCheck peut être appelé avant fusion pour signaler ces écarts récurrents :

using Access.It.Facturx.Services.Abstract;

public class PreFlight(IFacturXPdfaCompliancePreCheck preCheck)
{
    public void Inspect(byte[] sourcePdf)
    {
        var report = preCheck.Inspect(sourcePdf);
        foreach (var err in report.Errors)
            Console.WriteLine($"[{err.RuleId}] {err.Location} → {err.Message}");
    }
}

Détections couvertes :

  • ISO19005-1:6.3.4 — Type 1 built-in (Helvetica, Times-Roman, Courier...) utilisé sans embedding
  • ISO19005-1:6.3.5 — Font sans /FontFile, /FontFile2 ou /FontFile3 dans /FontDescriptor
  • ISO19005-1:6.3.7 — Type0 sans /DescendantFonts
  • ISO19005-1:6.3.8 — CIDFontType2 sans /CIDToGIDMap
  • ISO19005-1:6.4/Group /S=/Transparency sur une page
  • ISO19005-1:6.5.3 — Annotation sans /AP (warning)

Heuristique (pas un validateur ISO 19005-3 complet façon veraPDF), mais couvre 80% des cas récurrents avec Carbone / Word / Aspose.

Validation FNFE-MPE

Testé sur le validateur officiel services.fnfe-mpe.org :

  • ✅ Structure XSD CII conforme
  • ✅ XMP Factur-X (tous tags fx:* + pdfaid:part=3)
  • ✅ OutputIntent sRGB (résout les erreurs DeviceRGB/DeviceGray ISO 19005-1)
  • ✅ MIME type text/xml sur le filespec ET sur l'embedded file stream

Licence

Voir licence Access.It mono-repo.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.6.5 55 9/10/2026
10.6.4 45 9/10/2026
10.6.3 96 9/8/2026
10.6.2 89 9/8/2026
10.6.1 196 8/28/2026
10.6.0 96 8/28/2026
10.5.1 111 8/6/2026
10.5.0 108 8/4/2026
10.4.0 109 8/4/2026
10.3.1 109 7/30/2026
10.3.0 95 7/22/2026
10.2.0 107 7/22/2026
10.1.2 112 7/16/2026
10.1.1 109 7/15/2026
10.1.0 133 6/30/2026
10.0.3 120 6/19/2026
10.0.2 125 6/11/2026
10.0.2-beta.1 67 5/13/2026
10.0.1 130 3/29/2026
10.0.0 125 3/18/2026
Loading failed