Facture de marchandises étendue
Une facture de marchandises complète dans le profil EXTENDED, reconstruite étape par étape : six lignes avec indications de conditionnement et remises sur article, deux taux de TVA, des remises au niveau du document, des frais de transport, un escompte, ainsi qu’un destinataire des marchandises et un destinataire de la facture différents de l’acheteur.
Ce n’est pas un exemple quelconque, mais la facture d’exemple
officielle X19_01_Warenrechnung issue de la documentation
Factur-X / ZUGFeRD (jeu d’exemples FeRD, ZUGFeRD 2.5.0, profil
EXTENDED). Le code ci-dessous produit exactement ce document – champ par
champ, dans le même ordre. Placez le XML de référence à côté de la sortie et
vous verrez la même structure arborescente.
Certains liens de cette page mènent à la documentation approfondie dans l’espace client et demandent une connexion.
Sur le plan métier, il s’agit d’une livraison d’un grossiste en alimentation vers un magasin de supermarché. C’est précisément ce mélange qui rend l’exemple instructif : presque chaque champ qui soulève des questions au quotidien y apparaît au moins une fois.
Les sections ci-dessous s’enchaînent. Tous les extraits appartiennent à un seul programme et peuvent être écrits les uns après les autres dans cet ordre. Les commentaires citent à chaque fois les numéros de l’EN 16931 et de l’extension ZUGFeRD EXTENDED ; la référence vous donne pour chaque numéro l’élément XML correspondant.
Tous les champs EXTENDED de cet exemple – nom de document, indicateur de
test, destinataire de la facture, indications de conditionnement, composants
d’une unité de vente, frais de transport et bases d’imposition
étendues – ne sont écrits que si l’enregistrement final se fait
effectivement en Profile.Extended. Un profil plus petit n’est
pas une erreur ; les champs disparaissent alors simplement sans un mot.
1. En-tête de facture
CreateInvoice définit les trois mentions obligatoires de
l’en-tête : numéro de facture (BT-1), date de facture (BT-2) et devise de
facturation (BT-5). Tout le reste est ensuite rattaché à l’objet retourné.
// BT-1 numéro de facture, BT-2 date de facture, BT-5 devise de facturation
FacturXInvoice invoice = FacturXInvoice.CreateInvoice(
invoiceNo: "R87654321012345",
invoiceDate: new DateTime(2025, 10, 1),
currency: CurrencyCodes.EUR);
// BT-3 : type de facture 380 = facture commerciale. C'est la valeur par défaut, écrite ici
// explicitement pour montrer où le type est défini.
invoice.Type = InvoiceType.Invoice;
// BT-X-2 : nom de document libre. Autorisé et écrit uniquement dans le profil EXTENDED –
// en BASIC/COMFORT, le writer omet silencieusement l'élément.
invoice.Name = "WARENRECHNUNG";
// ram:TestIndicator marque le document comme jeu de test (également EXTENDED uniquement).
invoice.IsTest = true;
' BT-1 numéro de facture, BT-2 date de facture, BT-5 devise de facturation
Dim invoice As FacturXInvoice = FacturXInvoice.CreateInvoice(
invoiceNo:="R87654321012345",
invoiceDate:=New DateTime(2025, 10, 1),
currency:=CurrencyCodes.EUR)
' BT-3 : type de facture 380 = facture commerciale. C'est la valeur par défaut, écrite ici
' explicitement pour montrer où le type est défini.
invoice.Type = InvoiceType.Invoice
' BT-X-2 : nom de document libre. Autorisé et écrit uniquement dans le profil EXTENDED –
' en BASIC/COMFORT, le writer omet silencieusement l'élément.
invoice.Name = "WARENRECHNUNG"
' ram:TestIndicator marque le document comme jeu de test (également EXTENDED uniquement).
invoice.IsTest = True
2. Notes en texte libre
Le texte libre aboutit dans ram:IncludedNote (BG-1). Deux codes
pilotent son exploitabilité automatique : subjectCode (BT-21,
UNTDID 4451) dit de quoi parle la note, contentCode
(BT-X-5) est un bloc de texte normalisé supplémentaire.
Les deux codes ne se combinent pas librement :
ST1, ST2 et ST3 appartiennent à
AAK (accords de remise et de bonus), EEV,
WEB et VEV à AAJ (réserve de propriété).
// REG = mentions réglementaires, ici la représentation légale.
invoice.AddNote("Geschäftsführer: Herr Geschäftsführer , MUSTERLIEFERANT GmbH ",
subjectCode: SubjectCodes.REG);
// AAI = information générale.
invoice.AddNote("Es bestehen Vereinbarungen, aus denen sich Minderungen des Entgelts ergeben können.",
subjectCode: SubjectCodes.AAI);
// ACB = information complémentaire ; ici pour l'identification du format …
invoice.AddNote("ZUGFeRD vers 2.5.0 (Extended)",
subjectCode: SubjectCodes.ACB);
// … et pour indiquer qu'il s'agit d'un exemple.
invoice.AddNote("Dies ist ein Waren-Rechnungs-Beispiel",
subjectCode: SubjectCodes.ACB);
// AAK + ST3 : mention normalisée d'accords de remise ou de bonus existants.
invoice.AddNote("Es bestehen Rabatt- oder Bonusvereinbarungen.",
subjectCode: SubjectCodes.AAK,
contentCode: ContentCodes.ST3);
// AAJ + EEV : réserve de propriété normalisée.
invoice.AddNote("Der Verkäufer bleibt Eigentümer der Waren bis zu vollständigen Erfüllung der Kaufpreisforderung.",
subjectCode: SubjectCodes.AAJ,
contentCode: ContentCodes.EEV);
// Les notes sans subjectCode sont autorisées – texte libre pur.
invoice.AddNote("Leergutwert: 46,50");
invoice.AddNote("Wichtige Information: Bei Bestellungen bis zum 19.12. ist die Auslieferung bis spätestens 23.12. garantiert.");
' REG = mentions réglementaires, ici la représentation légale.
invoice.AddNote("Geschäftsführer: Herr Geschäftsführer , MUSTERLIEFERANT GmbH ",
subjectCode:=SubjectCodes.REG)
' AAI = information générale.
invoice.AddNote("Es bestehen Vereinbarungen, aus denen sich Minderungen des Entgelts ergeben können.",
subjectCode:=SubjectCodes.AAI)
' ACB = information complémentaire ; ici pour l'identification du format …
invoice.AddNote("ZUGFeRD vers 2.5.0 (Extended)",
subjectCode:=SubjectCodes.ACB)
' … et pour indiquer qu'il s'agit d'un exemple.
invoice.AddNote("Dies ist ein Waren-Rechnungs-Beispiel",
subjectCode:=SubjectCodes.ACB)
' AAK + ST3 : mention normalisée d'accords de remise ou de bonus existants.
invoice.AddNote("Es bestehen Rabatt- oder Bonusvereinbarungen.",
subjectCode:=SubjectCodes.AAK,
contentCode:=ContentCodes.ST3)
' AAJ + EEV : réserve de propriété normalisée.
invoice.AddNote("Der Verkäufer bleibt Eigentümer der Waren bis zu vollständigen Erfüllung der Kaufpreisforderung.",
subjectCode:=SubjectCodes.AAJ,
contentCode:=ContentCodes.EEV)
' Les notes sans subjectCode sont autorisées – texte libre pur.
invoice.AddNote("Leergutwert: 46,50")
invoice.AddNote("Wichtige Information: Bei Bestellungen bis zum 19.12. ist die Auslieferung bis spätestens 23.12. garantiert.")
Les textes des notes, les noms de produits et les motifs de remise restent en allemand sur toute cette page : ce sont les données de la facture de référence officielle, et les traduire romprait la comparaison avec elle.
3. Vendeur
SetSeller remplit ram:SellerTradeParty (BG-4). Outre
l’adresse, la partie porte deux identifiants : le numéro de fournisseur
interne (id, BT-29) et le GLN (globalID avec le schemeID
0088). Le contact, l’adresse électronique et
l’immatriculation fiscale s’ajoutent par des méthodes dédiées.
// id → ram:ID (BT-29) numéro de fournisseur interne
// globalID → ram:GlobalID (BT-29-0) avec schemeID "0088" = GLN
invoice.SetSeller(
name: "MUSTERLIEFERANT GMBH",
postcode: "99199",
city: "MUSTERHAUSEN",
street: "BAHNHOFSTRASSE 99",
country: CountryCodes.DE,
id: "549910",
globalID: new GlobalID(GlobalIDSchemeIdentifiers.GLN, "4333741000005"));
// BT-42 / BT-43 : interlocuteur. Nom et service restent vides – le writer n'émet alors
// pas du tout ram:PersonName / ram:DepartmentName.
invoice.SetSellerContact(
emailAddress: "max.mustermann@musterlieferant.de",
phoneno: "+49 932 431 500");
// BT-34 : adresse électronique du vendeur, ici un EAN Location Code (0088).
invoice.SetSellerElectronicAddress("info@musterlieferant.de",
ElectronicAddressSchemeIdentifiers.EanLocationCode);
// BT-31 : n° de TVA du vendeur (schéma VA). FC serait le numéro fiscal national.
invoice.AddSellerTaxRegistration("DE123456788", TaxRegistrationSchemeID.VA);
' id → ram:ID (BT-29) numéro de fournisseur interne
' globalID → ram:GlobalID (BT-29-0) avec schemeID "0088" = GLN
invoice.SetSeller(
name:="MUSTERLIEFERANT GMBH",
postcode:="99199",
city:="MUSTERHAUSEN",
street:="BAHNHOFSTRASSE 99",
country:=CountryCodes.DE,
id:="549910",
globalID:=New GlobalID(GlobalIDSchemeIdentifiers.GLN, "4333741000005"))
' BT-42 / BT-43 : interlocuteur. Nom et service restent vides – le writer n'émet alors
' pas du tout ram:PersonName / ram:DepartmentName.
invoice.SetSellerContact(
emailAddress:="max.mustermann@musterlieferant.de",
phoneno:="+49 932 431 500")
' BT-34 : adresse électronique du vendeur, ici un EAN Location Code (0088).
invoice.SetSellerElectronicAddress("info@musterlieferant.de",
ElectronicAddressSchemeIdentifiers.EanLocationCode)
' BT-31 : n° de TVA du vendeur (schéma VA). FC serait le numéro fiscal national.
invoice.AddSellerTaxRegistration("DE123456788", TaxRegistrationSchemeID.VA)
4. Acheteur
ram:BuyerTradeParty (BG-7) fonctionne de façon identique. Un point
important pour comprendre les sections suivantes : l’acheteur est ici le
siège – la livraison va à un magasin, la facture à un troisième site.
// L'acheteur est ici le siège ; destinataire des marchandises et de la facture
// en diffèrent et figurent plus bas.
invoice.SetBuyer(
name: "MUSTER-KUNDE GMBH",
postcode: "40235",
city: "KUNDENSTADT",
street: "KUNDENWEG 88",
country: CountryCodes.DE,
id: "009420",
globalID: new GlobalID(GlobalIDSchemeIdentifiers.GLN, "4304171000002"));
' L'acheteur est ici le siège ; destinataire des marchandises et de la facture
' en diffèrent et figurent plus bas.
invoice.SetBuyer(
name:="MUSTER-KUNDE GMBH",
postcode:="40235",
city:="KUNDENSTADT",
street:="KUNDENWEG 88",
country:=CountryCodes.DE,
id:="009420",
globalID:=New GlobalID(GlobalIDSchemeIdentifiers.GLN, "4304171000002"))
5. Références de documents dans l’en-tête
Quatre références rattachent la facture au reste du flux documentaire : commande, fiche de données de facturation, bon de livraison et date de livraison. D’autres types de référence – numéros de projet et de contrat par exemple – sont décrits dans Références de documents.
// BT-13 : numéro de commande de l'acheteur → ram:BuyerOrderReferencedDocument
invoice.SetBuyerOrderReferenceDocument("B123456789");
// BT-18 / BG-24 : référence de document supplémentaire. Code de type 130 = fiche de données
// (InvoiceDataSheet) – le numéro attribué par le fournisseur à cette fiche.
invoice.AddAdditionalReferencedDocument(
id: "A456123",
typeCode: AdditionalReferencedDocumentTypeCode.InvoiceDataSheet);
// BT-16 : numéro de bon de livraison → ram:DeliveryNoteReferencedDocument (EXTENDED)
invoice.SetDeliveryNoteReferenceDocument("L87654321012345");
// BT-72 : date de livraison effective → ram:ActualDeliverySupplyChainEvent
invoice.ActualDeliveryDate = new DateTime(2025, 10, 1);
' BT-13 : numéro de commande de l'acheteur → ram:BuyerOrderReferencedDocument
invoice.SetBuyerOrderReferenceDocument("B123456789")
' BT-18 / BG-24 : référence de document supplémentaire. Code de type 130 = fiche de données
' (InvoiceDataSheet) – le numéro attribué par le fournisseur à cette fiche.
invoice.AddAdditionalReferencedDocument(
id:="A456123",
typeCode:=AdditionalReferencedDocumentTypeCode.InvoiceDataSheet)
' BT-16 : numéro de bon de livraison → ram:DeliveryNoteReferencedDocument (EXTENDED)
invoice.SetDeliveryNoteReferenceDocument("L87654321012345")
' BT-72 : date de livraison effective → ram:ActualDeliverySupplyChainEvent
invoice.ActualDeliveryDate = New DateTime(2025, 10, 1)
6. Destinataire des marchandises et de la facture
Le destinataire des marchandises (BG-13) et celui de la facture (BG-X-36) ne sont
pas définis par des méthodes Set… mais comme des objets
Party. Le destinataire des marchandises ne porte ici qu’un GLN et
aucune ID interne ; ShipToContact.OrgUnit ajoute en plus le service du
magasin au document.
// BG-13 ram:ShipToTradeParty : la livraison va au magasin, pas au siège.
// Il n'y a pas d'ID interne ici, seulement un GLN.
invoice.ShipTo = new Party()
{
GlobalID = new GlobalID(GlobalIDSchemeIdentifiers.GLN, "4304171088093"),
Name = "MUSTER-MARKT",
Postcode = "31157",
Street = "HAUPTSTRASSE 44",
City = "SARSTEDT",
Country = CountryCodes.DE
};
// ram:DefinedTradeContact/ram:DepartmentName – le service ("8211") dans le magasin.
// OrgUnit correspond exactement à ce champ ; nom, téléphone et e-mail restent vides.
invoice.ShipToContact = new Contact()
{
OrgUnit = "8211"
};
// BG-X-36 ram:InvoiceeTradeParty (EXTENDED uniquement) : même société que l'acheteur,
// mais la facture va au site de Düsseldorf.
invoice.Invoicee = new Party()
{
ID = new GlobalID(null, "009420"),
GlobalID = new GlobalID(GlobalIDSchemeIdentifiers.GLN, "4304171000002"),
Name = "MUSTER-KUNDE GMBH",
Postcode = "40235",
Street = "KUNDENWEG 88",
City = "DUESSELDORF",
Country = CountryCodes.DE
};
' BG-13 ram:ShipToTradeParty : la livraison va au magasin, pas au siège.
' Il n'y a pas d'ID interne ici, seulement un GLN.
invoice.ShipTo = New Party() With
{
.GlobalID = New GlobalID(GlobalIDSchemeIdentifiers.GLN, "4304171088093"),
.Name = "MUSTER-MARKT",
.Postcode = "31157",
.Street = "HAUPTSTRASSE 44",
.City = "SARSTEDT",
.Country = CountryCodes.DE
}
' ram:DefinedTradeContact/ram:DepartmentName – le service ("8211") dans le magasin.
' OrgUnit correspond exactement à ce champ ; nom, téléphone et e-mail restent vides.
invoice.ShipToContact = New Contact() With
{
.OrgUnit = "8211"
}
' BG-X-36 ram:InvoiceeTradeParty (EXTENDED uniquement) : même société que l'acheteur,
' mais la facture va au site de Düsseldorf.
invoice.Invoicee = New Party() With
{
.ID = New GlobalID(Nothing, "009420"),
.GlobalID = New GlobalID(GlobalIDSchemeIdentifiers.GLN, "4304171000002"),
.Name = "MUSTER-KUNDE GMBH",
.Postcode = "40235",
.Street = "KUNDENWEG 88",
.City = "DUESSELDORF",
.Country = CountryCodes.DE
}
7. Lignes de facture
Six lignes (BG-25), chacune avec sa particularité. Le motif récurrent est le même dans les six :
| Paramètre | Code BT | Signification |
|---|---|---|
lineID |
BT-126 | Numéro de ligne. S’il est omis, FactoorSharp l’attribue automatiquement – voir Numéros de ligne. |
id |
BT-157 | GTIN avec le schemeID 0160 (GS1), en FactoorSharp GlobalIDSchemeIdentifiers.EAN. |
sellerAssignedID |
BT-155 | Référence article du fournisseur. |
buyerAssignedID |
BT-156 | Référence article de l’acheteur pour la même marchandise. |
grossUnitPrice |
BT-148 | Prix brut, c’est-à-dire le prix catalogue avant remise sur article. |
netUnitPrice |
BT-146 | Prix net après remise sur article – la base du total de ligne. |
lineTotalAmount |
BT-131 | Total de ligne = netUnitPrice × billedQuantity. |
PackageQuantity / PackageUnitCode |
BT-X-9 | Unité d’expédition, EXTENDED uniquement. XCT = carton, XBC = caisse, XBO = bouteille, XPX = palette. |
Ligne 1 – attribut d’article et unité d’expédition
La ligne la plus simple : aucune remise, un attribut d’article (BG-32) et l’indication du nombre de cartons dans lesquels la marchandise est livrée. Les attributs d’article ont une page à eux : Caractéristiques produit.
// 100 bouteilles d'acide citrique à 1,00 € = 100,00 €, 19 % de TVA
TradeLineItem line1 = invoice.AddTradeLineItem(
lineID: "1",
name: "Zitronensäure 100ml",
netUnitPrice: 1.00m,
grossUnitPrice: 1.00m,
unitCode: QuantityCodes.H87, // H87 = pièce
billedQuantity: 100m,
lineTotalAmount: 100.00m,
taxType: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S, // S = taux normal
taxPercent: 19m,
sellerAssignedID: "ZS997",
id: new GlobalID(GlobalIDSchemeIdentifiers.EAN, "4123456000014"));
// BG-32 attribut d'article : paire nom/valeur librement définissable (BT-160 / BT-161).
line1.ApplicableProductCharacteristics.Add(new ApplicableProductCharacteristic()
{
Description = "Verpackungsart",
Value = "BO"
});
// 4 cartons comme unité d'expédition.
line1.PackageQuantity = 4m;
line1.PackageUnitCode = QuantityCodes.XCT;
' 100 bouteilles d'acide citrique à 1,00 € = 100,00 €, 19 % de TVA
Dim line1 As TradeLineItem = invoice.AddTradeLineItem(
lineID:="1",
name:="Zitronensäure 100ml",
netUnitPrice:=1.0D,
grossUnitPrice:=1.0D,
unitCode:=QuantityCodes.H87, ' H87 = pièce
billedQuantity:=100D,
lineTotalAmount:=100.0D,
taxType:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S, ' S = taux normal
taxPercent:=19D,
sellerAssignedID:="ZS997",
id:=New GlobalID(GlobalIDSchemeIdentifiers.EAN, "4123456000014"))
' BG-32 attribut d'article : paire nom/valeur librement définissable (BT-160 / BT-161).
line1.ApplicableProductCharacteristics.Add(New ApplicableProductCharacteristic() With
{
.Description = "Verpackungsart",
.Value = "BO"
})
' 4 cartons comme unité d'expédition.
line1.PackageQuantity = 4D
line1.PackageUnitCode = QuantityCodes.XCT
Ligne 2 – remises sur article dans le prix brut
Ici, prix brut et prix net diffèrent parce que deux remises sur article entrent en
jeu. Ces remises ne se situent pas au niveau du document mais comme
ram:AppliedTradeAllowanceCharge à l’intérieur du prix brut
(BT-147).
Les montants s’entendent par unité, pas pour toute la ligne : 1,50 − 0,03 − 0,02 = 1,45. FactoorSharp ne calcule pas le prix net – vous le définissez, et les remises l’expliquent.
// 50 × sucre gélifiant, prix catalogue 1,50 € moins deux remises sur article
// (0,03 € + 0,02 €) → prix net 1,45 € ; 50 × 1,45 = 72,50 €, 7 %.
TradeLineItem line2 = invoice.AddTradeLineItem(
lineID: "2",
name: "Gelierzucker Extra 250g",
netUnitPrice: 1.45m,
grossUnitPrice: 1.50m,
unitCode: QuantityCodes.H87,
billedQuantity: 50m,
lineTotalAmount: 72.50m,
taxType: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S,
taxPercent: 7m,
sellerAssignedID: "GZ250",
id: new GlobalID(GlobalIDSchemeIdentifiers.EAN, "4123456000021"));
// BT-147 : les remises sur article sont écrites comme ram:AppliedTradeAllowanceCharge À
// L'INTÉRIEUR du prix brut. Les montants valent PAR UNITÉ, pas pour toute la
// ligne. Le prix net ci-dessus en est le résultat : 1,50 − 0,03 − 0,02 = 1,45.
line2.AddTradeAllowance(CurrencyCodes.EUR, basisAmount: null, actualAmount: 0.03m, reason: "Artikelrabatt 1");
line2.AddTradeAllowance(CurrencyCodes.EUR, basisAmount: null, actualAmount: 0.02m, reason: "Artikelrabatt 2");
line2.PackageQuantity = 1m;
line2.PackageUnitCode = QuantityCodes.XCT;
' 50 × sucre gélifiant, prix catalogue 1,50 € moins deux remises sur article
' (0,03 € + 0,02 €) → prix net 1,45 € ; 50 × 1,45 = 72,50 €, 7 %.
Dim line2 As TradeLineItem = invoice.AddTradeLineItem(
lineID:="2",
name:="Gelierzucker Extra 250g",
netUnitPrice:=1.45D,
grossUnitPrice:=1.5D,
unitCode:=QuantityCodes.H87,
billedQuantity:=50D,
lineTotalAmount:=72.5D,
taxType:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S,
taxPercent:=7D,
sellerAssignedID:="GZ250",
id:=New GlobalID(GlobalIDSchemeIdentifiers.EAN, "4123456000021"))
' BT-147 : les remises sur article sont écrites comme ram:AppliedTradeAllowanceCharge À
' L'INTÉRIEUR du prix brut. Les montants valent PAR UNITÉ, pas pour toute la
' ligne. Le prix net ci-dessus en est le résultat : 1,50 − 0,03 − 0,02 = 1,45.
line2.AddTradeAllowance(CurrencyCodes.EUR, basisAmount:=Nothing, actualAmount:=0.03D, reason:="Artikelrabatt 1")
line2.AddTradeAllowance(CurrencyCodes.EUR, basisAmount:=Nothing, actualAmount:=0.02D, reason:="Artikelrabatt 2")
line2.PackageQuantity = 1D
line2.PackageUnitCode = QuantityCodes.XCT
Ligne 3 – remise en nature
Dix pièces de la même marchandise sans facturation. La ligne reste dans le document pour que la quantité livrée reste traçable, mais contribue 0,00 € au total. Le motif figure à côté comme description de ligne (BT-154).
// Remise en nature : 10 pièces du même article à 0,00 €. La ligne reste dans le
// document (traçabilité de la quantité livrée), mais contribue 0,00 € au total.
TradeLineItem line3 = invoice.AddTradeLineItem(
lineID: "3",
name: "Gelierzucker Extra 250g",
description: "Artikel wie vereinbart ohne Berechnung", // BT-154
netUnitPrice: 0.00m,
grossUnitPrice: 0.00m,
unitCode: QuantityCodes.H87,
billedQuantity: 10m,
lineTotalAmount: 0.00m,
taxType: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S,
taxPercent: 7m,
sellerAssignedID: "GZ250",
id: new GlobalID(GlobalIDSchemeIdentifiers.EAN, "4123456000021"));
line3.PackageQuantity = 1m;
line3.PackageUnitCode = QuantityCodes.XCT;
' Remise en nature : 10 pièces du même article à 0,00 €. La ligne reste dans le
' document (traçabilité de la quantité livrée), mais contribue 0,00 € au total.
Dim line3 As TradeLineItem = invoice.AddTradeLineItem(
lineID:="3",
name:="Gelierzucker Extra 250g",
description:="Artikel wie vereinbart ohne Berechnung", ' BT-154
netUnitPrice:=0D,
grossUnitPrice:=0D,
unitCode:=QuantityCodes.H87,
billedQuantity:=10D,
lineTotalAmount:=0D,
taxType:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S,
taxPercent:=7D,
sellerAssignedID:="GZ250",
id:=New GlobalID(GlobalIDSchemeIdentifiers.EAN, "4123456000021"))
line3.PackageQuantity = 1D
line3.PackageUnitCode = QuantityCodes.XCT
Lignes 4 et 5 – unités divergentes et référence acheteur
La ligne 4 montre que l’unité de facturation et l’unité d’expédition peuvent différer : facturée en caisses, expédiée en bouteilles. La ligne 5 est la consigne d’emballages correspondante et porte en plus la référence article de l’acheteur (BT-156).
// 15 caisses de bière à 12,00 € = 180,00 €, 19 %. L'unité de facturation est la caisse (XBC),
// l'unité d'expédition la bouteille (XBO) – 20 bouteilles par caisse.
TradeLineItem line4 = invoice.AddTradeLineItem(
lineID: "4",
name: "Bierbrau Pils 20/0500",
description: "EAN-VKE: 4100130913297",
netUnitPrice: 12.00m,
grossUnitPrice: 12.00m,
unitCode: QuantityCodes.XBC, // XBC = caisse
billedQuantity: 15m,
lineTotalAmount: 180.00m,
taxType: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S,
taxPercent: 19m,
sellerAssignedID: "2031",
id: new GlobalID(GlobalIDSchemeIdentifiers.EAN, "4100130013294"));
line4.ApplicableProductCharacteristics.Add(new ApplicableProductCharacteristic()
{
Description = "Verpackung",
Value = "Kiste"
});
line4.PackageQuantity = 20m;
line4.PackageUnitCode = QuantityCodes.XBO; // XBO = bouteille
// Consigne d'emballages, 15 × 3,10 € = 46,50 €, 19 %. buyerAssignedID (BT-156) montre que
// l'acheteur tient son propre numéro pour le même article.
TradeLineItem line5 = invoice.AddTradeLineItem(
lineID: "5",
name: "Leergutpfand 20 x 0,5l",
netUnitPrice: 3.10m,
grossUnitPrice: 3.10m,
unitCode: QuantityCodes.H87,
billedQuantity: 15m,
lineTotalAmount: 46.50m,
taxType: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S,
taxPercent: 19m,
sellerAssignedID: "1805",
buyerAssignedID: "4711",
id: new GlobalID(GlobalIDSchemeIdentifiers.EAN, "2001015001325"));
line5.ApplicableProductCharacteristics.Add(new ApplicableProductCharacteristic()
{
Description = "Verpackung",
Value = "unverpackt"
});
line5.PackageQuantity = 1m;
line5.PackageUnitCode = QuantityCodes.XBC;
' 15 caisses de bière à 12,00 € = 180,00 €, 19 %. L'unité de facturation est la caisse (XBC),
' l'unité d'expédition la bouteille (XBO) – 20 bouteilles par caisse.
Dim line4 As TradeLineItem = invoice.AddTradeLineItem(
lineID:="4",
name:="Bierbrau Pils 20/0500",
description:="EAN-VKE: 4100130913297",
netUnitPrice:=12D,
grossUnitPrice:=12D,
unitCode:=QuantityCodes.XBC, ' XBC = caisse
billedQuantity:=15D,
lineTotalAmount:=180D,
taxType:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S,
taxPercent:=19D,
sellerAssignedID:="2031",
id:=New GlobalID(GlobalIDSchemeIdentifiers.EAN, "4100130013294"))
line4.ApplicableProductCharacteristics.Add(New ApplicableProductCharacteristic() With
{
.Description = "Verpackung",
.Value = "Kiste"
})
line4.PackageQuantity = 20D
line4.PackageUnitCode = QuantityCodes.XBO ' XBO = bouteille
' Consigne d'emballages, 15 × 3,10 € = 46,50 €, 19 %. buyerAssignedID (BT-156) montre que
' l'acheteur tient son propre numéro pour le même article.
Dim line5 As TradeLineItem = invoice.AddTradeLineItem(
lineID:="5",
name:="Leergutpfand 20 x 0,5l",
netUnitPrice:=3.1D,
grossUnitPrice:=3.1D,
unitCode:=QuantityCodes.H87,
billedQuantity:=15D,
lineTotalAmount:=46.5D,
taxType:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S,
taxPercent:=19D,
sellerAssignedID:="1805",
buyerAssignedID:="4711",
id:=New GlobalID(GlobalIDSchemeIdentifiers.EAN, "2001015001325"))
line5.ApplicableProductCharacteristics.Add(New ApplicableProductCharacteristic() With
{
.Description = "Verpackung",
.Value = "unverpackt"
})
line5.PackageQuantity = 1D
line5.PackageUnitCode = QuantityCodes.XBC
Ligne 6 – palette mixte et ses composants
La palette est facturée comme une seule ligne, mais son contenu est décomposé via
IncludedReferencedProducts (BG-X-1). Cette indication est purement
informative : les prix et les taxes restent attachés à la ligne parente.
// Palette mixte de trois sortes de yaourt. Prix catalogue 30,00 € moins 0,90 €
// de remise sur article → 29,10 € ; 2 × 29,10 = 58,20 €, 7 %.
TradeLineItem line6 = invoice.AddTradeLineItem(
lineID: "6",
name: "Mischpalette Joghurt Karton 3 x 20",
netUnitPrice: 29.10m,
grossUnitPrice: 30.00m,
unitCode: QuantityCodes.H87,
billedQuantity: 2m,
lineTotalAmount: 58.20m,
taxType: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S,
taxPercent: 7m,
sellerAssignedID: "MP107",
id: new GlobalID(GlobalIDSchemeIdentifiers.EAN, "4123456000038"));
line6.ApplicableProductCharacteristics.Add(new ApplicableProductCharacteristic()
{
Description = "Verpackung",
Value = "Karton"
});
// BG-X-1 ram:IncludedReferencedProduct (EXTENDED uniquement) : décomposition d'une
// unité de vente en ses composants. Purement informatif – prix et taxes restent
// attachés à la ligne parente.
//
// La méthode pratique AddIncludedReferencedProduct() ne connaît pas GlobalID, c'est pourquoi
// les objets sont créés directement ici.
line6.IncludedReferencedProducts.Add(new IncludedReferencedProduct()
{
GlobalID = new GlobalID(GlobalIDSchemeIdentifiers.EAN, "4123456001035"),
SellerAssignedID = "JOG103",
Name = "Erdbeer 20 x 150g Becher",
UnitQuantity = 20m,
UnitCode = QuantityCodes.H87
});
line6.IncludedReferencedProducts.Add(new IncludedReferencedProduct()
{
GlobalID = new GlobalID(GlobalIDSchemeIdentifiers.EAN, "4123456002032"),
SellerAssignedID = "JOG203",
Name = "Banane 20 x 150g Becher",
UnitQuantity = 20m,
UnitCode = QuantityCodes.H87
});
line6.IncludedReferencedProducts.Add(new IncludedReferencedProduct()
{
GlobalID = new GlobalID(GlobalIDSchemeIdentifiers.EAN, "4123456003039"),
SellerAssignedID = "JOG303",
Name = "Schoko 20 x 150g Becher",
UnitQuantity = 20m,
UnitCode = QuantityCodes.H87
});
// BT-147 comme à la ligne 2 : remise par unité, 30,00 − 0,90 = 29,10.
line6.AddTradeAllowance(CurrencyCodes.EUR, basisAmount: null, actualAmount: 0.90m, reason: "Artikelrabatt 1");
line6.PackageQuantity = 1m;
line6.PackageUnitCode = QuantityCodes.XPX; // XPX = palette
' Palette mixte de trois sortes de yaourt. Prix catalogue 30,00 € moins 0,90 €
' de remise sur article → 29,10 € ; 2 × 29,10 = 58,20 €, 7 %.
Dim line6 As TradeLineItem = invoice.AddTradeLineItem(
lineID:="6",
name:="Mischpalette Joghurt Karton 3 x 20",
netUnitPrice:=29.1D,
grossUnitPrice:=30D,
unitCode:=QuantityCodes.H87,
billedQuantity:=2D,
lineTotalAmount:=58.2D,
taxType:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S,
taxPercent:=7D,
sellerAssignedID:="MP107",
id:=New GlobalID(GlobalIDSchemeIdentifiers.EAN, "4123456000038"))
line6.ApplicableProductCharacteristics.Add(New ApplicableProductCharacteristic() With
{
.Description = "Verpackung",
.Value = "Karton"
})
' BG-X-1 ram:IncludedReferencedProduct (EXTENDED uniquement) : décomposition d'une
' unité de vente en ses composants. Purement informatif – prix et taxes restent
' attachés à la ligne parente.
'
' La méthode pratique AddIncludedReferencedProduct() ne connaît pas GlobalID, c'est pourquoi
' les objets sont créés directement ici.
line6.IncludedReferencedProducts.Add(New IncludedReferencedProduct() With
{
.GlobalID = New GlobalID(GlobalIDSchemeIdentifiers.EAN, "4123456001035"),
.SellerAssignedID = "JOG103",
.Name = "Erdbeer 20 x 150g Becher",
.UnitQuantity = 20D,
.UnitCode = QuantityCodes.H87
})
line6.IncludedReferencedProducts.Add(New IncludedReferencedProduct() With
{
.GlobalID = New GlobalID(GlobalIDSchemeIdentifiers.EAN, "4123456002032"),
.SellerAssignedID = "JOG203",
.Name = "Banane 20 x 150g Becher",
.UnitQuantity = 20D,
.UnitCode = QuantityCodes.H87
})
line6.IncludedReferencedProducts.Add(New IncludedReferencedProduct() With
{
.GlobalID = New GlobalID(GlobalIDSchemeIdentifiers.EAN, "4123456003039"),
.SellerAssignedID = "JOG303",
.Name = "Schoko 20 x 150g Becher",
.UnitQuantity = 20D,
.UnitCode = QuantityCodes.H87
})
' BT-147 comme à la ligne 2 : remise par unité, 30,00 − 0,90 = 29,10.
line6.AddTradeAllowance(CurrencyCodes.EUR, basisAmount:=Nothing, actualAmount:=0.9D, reason:="Artikelrabatt 1")
line6.PackageQuantity = 1D
line6.PackageUnitCode = QuantityCodes.XPX ' XPX = palette
8. Remises au niveau du document
Deux remises – l’une en pourcentage, l’autre en montant fixe
– sont représentées par ram:SpecifiedTradeAllowanceCharge
(BG-20).
Une remise au niveau du document ne vaut jamais que pour un taux de TVA. Comme ce document mêle 19 % et 7 %, chaque remise doit être créée deux fois, avec la base de calcul correspondante. Deux remises deviennent ainsi quatre appels. L’ordre dans le XML correspond à l’ordre des appels dans le code.
Les bases de calcul ne sont pas simplement les totaux de ligne : pour 19 %, les lignes 1, 4 et 5 donneraient ensemble 326,50 €, mais seuls 280,00 € ont été convenus – la consigne d’emballages et une partie de la marchandise sont exclues de la remise. Pour 7 %, c’est le total complet des lignes 2, 3 et 6 qui s’applique : 130,70 €.
// Remise sur facture 1 – en pourcentage, 2 % (BT-94 CalculationPercent + BT-93 BasisAmount).
invoice.AddTradeAllowance(
basisAmount: 280.00m,
currency: CurrencyCodes.EUR,
actualAmount: 5.60m, // 2 % de 280,00
chargePercentage: 2.00m,
reason: "Rechnungsrabatt 1",
taxTypeCode: TaxTypes.VAT,
taxCategoryCode: TaxCategoryCodes.S,
taxPercent: 19m);
invoice.AddTradeAllowance(
basisAmount: 130.70m,
currency: CurrencyCodes.EUR,
actualAmount: 2.61m, // 2 % de 130,70 (arrondi commercial)
chargePercentage: 2.00m,
reason: "Rechnungsrabatt 1",
taxTypeCode: TaxTypes.VAT,
taxCategoryCode: TaxCategoryCodes.S,
taxPercent: 7m);
// Remise sur facture 2 – montant fixe, donc sans chargePercentage.
invoice.AddTradeAllowance(
basisAmount: 280.00m,
currency: CurrencyCodes.EUR,
actualAmount: 2.50m,
reason: "Rechnungsrabatt 2",
taxTypeCode: TaxTypes.VAT,
taxCategoryCode: TaxCategoryCodes.S,
taxPercent: 19m);
invoice.AddTradeAllowance(
basisAmount: 130.70m,
currency: CurrencyCodes.EUR,
actualAmount: 0.50m,
reason: "Rechnungsrabatt 2",
taxTypeCode: TaxTypes.VAT,
taxCategoryCode: TaxCategoryCodes.S,
taxPercent: 7m);
' Remise sur facture 1 – en pourcentage, 2 % (BT-94 CalculationPercent + BT-93 BasisAmount).
invoice.AddTradeAllowance(
basisAmount:=280D,
currency:=CurrencyCodes.EUR,
actualAmount:=5.6D, ' 2 % de 280,00
chargePercentage:=2D,
reason:="Rechnungsrabatt 1",
taxTypeCode:=TaxTypes.VAT,
taxCategoryCode:=TaxCategoryCodes.S,
taxPercent:=19D)
invoice.AddTradeAllowance(
basisAmount:=130.7D,
currency:=CurrencyCodes.EUR,
actualAmount:=2.61D, ' 2 % de 130,70 (arrondi commercial)
chargePercentage:=2D,
reason:="Rechnungsrabatt 1",
taxTypeCode:=TaxTypes.VAT,
taxCategoryCode:=TaxCategoryCodes.S,
taxPercent:=7D)
' Remise sur facture 2 – montant fixe, donc sans chargePercentage.
invoice.AddTradeAllowance(
basisAmount:=280D,
currency:=CurrencyCodes.EUR,
actualAmount:=2.5D,
reason:="Rechnungsrabatt 2",
taxTypeCode:=TaxTypes.VAT,
taxCategoryCode:=TaxCategoryCodes.S,
taxPercent:=19D)
invoice.AddTradeAllowance(
basisAmount:=130.7D,
currency:=CurrencyCodes.EUR,
actualAmount:=0.5D,
reason:="Rechnungsrabatt 2",
taxTypeCode:=TaxTypes.VAT,
taxCategoryCode:=TaxCategoryCodes.S,
taxPercent:=7D)
9. Frais de transport
Les frais logistiques ont un élément dédié en CII et ne sont pas la même chose
qu’un supplément ordinaire via AddTradeCharge. Dans
XRechnung,
l’élément n’est pas autorisé ; en ZUGFeRD EXTENDED, il l’est.
// ram:SpecifiedLogisticsServiceCharge – élément dédié aux frais logistiques, PAS
// identique à un supplément normal (AddTradeCharge). Interdit dans XRechnung,
// autorisé dans ZUGFeRD EXTENDED. Alimente ChargeTotalAmount (BT-108).
invoice.AddLogisticsServiceCharge(
amount: 3.00m,
description: "Transportkosten",
taxTypeCode: TaxTypes.VAT,
taxCategoryCode: TaxCategoryCodes.S,
taxPercent: 19m);
' ram:SpecifiedLogisticsServiceCharge – élément dédié aux frais logistiques, PAS
' identique à un supplément normal (AddTradeCharge). Interdit dans XRechnung,
' autorisé dans ZUGFeRD EXTENDED. Alimente ChargeTotalAmount (BT-108).
invoice.AddLogisticsServiceCharge(
amount:=3D,
description:="Transportkosten",
taxTypeCode:=TaxTypes.VAT,
taxCategoryCode:=TaxCategoryCodes.S,
taxPercent:=19D)
10. Ventilation de la TVA
Un groupe ram:ApplicableTradeTax (BG-23) est obligatoire par
combinaison de taux et de catégorie de TVA – donc deux ici. FactoorSharp ne
fait pas les sommes lui-même ; les valeurs viennent de votre comptabilité et sont
définies explicitement. Le calcul de ce document :
| Champ | 19 % (lignes 1, 4, 5) | 7 % (lignes 2, 3, 6) |
|---|---|---|
lineTotalBasisAmount |
326,50 | 130,70 |
allowanceChargeBasisAmount |
−5,60 − 2,50 + 3,00 = −5,10 | −2,61 − 0,50 = −3,11 |
basisAmount |
326,50 − 5,10 = 321,40 | 130,70 − 3,11 = 127,59 |
taxAmount |
321,40 × 19 % = 61,066 → 61,07 | 127,59 × 7 % = 8,9313 → 8,93 |
Le supplément pour les frais de transport entre dans la ligne à 19 % avec un signe positif, les remises avec un signe négatif. Pour en savoir plus sur les montants de TVA, en particulier avec une devise fiscale divergente, voir Montants de TVA.
// lineTotalBasisAmount et allowanceChargeBasisAmount sont des champs EXTENDED ;
// en BASIC/COMFORT, ils disparaissent sans remplacement.
invoice.AddApplicableTradeTax(
basisAmount: 321.40m,
percent: 19m,
taxAmount: 61.07m,
typeCode: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S,
allowanceChargeBasisAmount: -5.10m,
lineTotalBasisAmount: 326.50m);
invoice.AddApplicableTradeTax(
basisAmount: 127.59m,
percent: 7m,
taxAmount: 8.93m,
typeCode: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S,
allowanceChargeBasisAmount: -3.11m,
lineTotalBasisAmount: 130.70m);
' lineTotalBasisAmount et allowanceChargeBasisAmount sont des champs EXTENDED ;
' en BASIC/COMFORT, ils disparaissent sans remplacement.
invoice.AddApplicableTradeTax(
basisAmount:=321.4D,
percent:=19D,
taxAmount:=61.07D,
typeCode:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S,
allowanceChargeBasisAmount:=-5.1D,
lineTotalBasisAmount:=326.5D)
invoice.AddApplicableTradeTax(
basisAmount:=127.59D,
percent:=7D,
taxAmount:=8.93D,
typeCode:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S,
allowanceChargeBasisAmount:=-3.11D,
lineTotalBasisAmount:=130.7D)
11. Condition de paiement avec escompte
L’escompte ne figure pas seulement comme phrase en texte libre, mais aussi de
façon exploitable automatiquement dans le XML – c’est le rôle de
PaymentTermsType.Skonto.
// paymentTermsType = Skonto crée le bloc ram:ApplicableTradePaymentDiscountTerms
// avec BasisPeriodMeasure (jours) et CalculationPercent. Sans ce type, il ne resterait que le
// texte libre – l'escompte ne serait alors pas exploitable automatiquement.
invoice.AddTradePaymentTerms(
description: "Bei Zahlung innerhalb 14 Tagen gewähren wir 2,0% Skonto.",
paymentTermsType: PaymentTermsType.Skonto,
dueDays: 14,
percentage: 2.00m);
' paymentTermsType = Skonto crée le bloc ram:ApplicableTradePaymentDiscountTerms
' avec BasisPeriodMeasure (jours) et CalculationPercent. Sans ce type, il ne resterait que le
' texte libre – l'escompte ne serait alors pas exploitable automatiquement.
invoice.AddTradePaymentTerms(
description:="Bei Zahlung innerhalb 14 Tagen gewähren wir 2,0% Skonto.",
paymentTermsType:=PaymentTermsType.Skonto,
dueDays:=14,
percentage:=2D)
12. Totaux du document
SetTotals écrit ram:SpecifiedTradeSettlementHeaderMonetarySummation
(BG-22). Ces valeurs aussi sont définies, pas calculées :
| Code BT | Paramètre | Calcul |
|---|---|---|
| BT-106 | lineTotalAmount |
100,00 + 72,50 + 0,00 + 180,00 + 46,50 + 58,20 = 457,20 |
| BT-108 | chargeTotalAmount |
3,00 (frais de transport) |
| BT-107 | allowanceTotalAmount |
5,60 + 2,61 + 2,50 + 0,50 = 11,21 |
| BT-109 | taxBasisAmount |
457,20 + 3,00 − 11,21 = 448,99 |
| BT-110 | taxTotalAmount |
61,07 + 8,93 = 70,00 |
| BT-112 | grandTotalAmount |
448,99 + 70,00 = 518,99 |
| BT-113 | totalPrepaidAmount |
0,00 – aucun acompte. La façon de détailler les acomptes est décrite sous Acomptes. |
| BT-115 | duePayableAmount |
518,99 − 0,00 = 518,99 |
invoice.SetTotals(
lineTotalAmount: 457.20m, // BT-106
chargeTotalAmount: 3.00m, // BT-108
allowanceTotalAmount: 11.21m, // BT-107
taxBasisAmount: 448.99m, // BT-109
taxTotalAmount: 70.00m, // BT-110
grandTotalAmount: 518.99m, // BT-112
totalPrepaidAmount: 0.00m, // BT-113
duePayableAmount: 518.99m); // BT-115
invoice.SetTotals(
lineTotalAmount:=457.2D, ' BT-106
chargeTotalAmount:=3D, ' BT-108
allowanceTotalAmount:=11.21D, ' BT-107
taxBasisAmount:=448.99D, ' BT-109
taxTotalAmount:=70D, ' BT-110
grandTotalAmount:=518.99D, ' BT-112
totalPrepaidAmount:=0D, ' BT-113
duePayableAmount:=518.99D) ' BT-115
13. Enregistrement
Ce n’est qu’à l’enregistrement que se décide quels champs définis ci-dessus atterrissent réellement dans le XML. Version, profil et syntaxe vont ensemble :
// Version25 + Profile.Extended + CII donne l'identifiant de profil
// "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended".
// Un autre profil écarterait silencieusement tous les champs EXTENDED ci-dessus.
invoice.Save("X19_01_Warenrechnung.xml",
ZUGFeRDVersion.Version25,
Profile.Extended,
ZUGFeRDFormats.CII);
' Version25 + Profile.Extended + CII donne l'identifiant de profil
' "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended".
' Un autre profil écarterait silencieusement tous les champs EXTENDED ci-dessus.
invoice.Save("X19_01_Warenrechnung.xml",
ZUGFeRDVersion.Version25,
Profile.Extended,
ZUGFeRDFormats.CII)
La façon de produire la même facture en PDF/A-3 avec XML intégré est décrite dans Travailler avec des fichiers PDF ; les autres manières d’enregistrer figurent sous Charger et enregistrer.
14. Contrôle croisé
Comme la cible est un fichier de référence connu, le résultat se vérifie
directement : le fichier produit doit présenter la même structure arborescente que
X19_01_Warenrechnung.xml du jeu d’exemples FeRD. Les nombres se
comparent numériquement, de sorte que 1.00 et 1.0000
comptent comme égaux – FactoorSharp écrit les prix de manière adaptative avec
deux décimales.
Quant à savoir si la facture respecte également le jeu de règles, vous le vérifiez soit dans le navigateur, soit directement depuis votre code. Pour le regard d’un humain sur le document, le visualiseur produit une version lisible :
// Version lisible par un humain – ni document juridique ni PDF ZUGFeRD hybride.
// Le rendu se fait à partir de l'objet, pas du fichier XML.
InvoiceVisualizer.RenderPdf(invoice, "X19_01_Warenrechnung.pdf");
InvoiceVisualizer.RenderHtml(invoice, "X19_01_Warenrechnung.html");
' Version lisible par un humain – ni document juridique ni PDF ZUGFeRD hybride.
' Le rendu se fait à partir de l'objet, pas du fichier XML.
InvoiceVisualizer.RenderPdf(invoice, "X19_01_Warenrechnung.pdf")
InvoiceVisualizer.RenderHtml(invoice, "X19_01_Warenrechnung.html")
C’est une simple vue, ni document juridique ni PDF ZUGFeRD hybride – détails sous Représentation visuelle.