TVA allemande sur les pièces usagées (Altteilesteuer)
Une règle du droit allemand de la TVA : le garage livre un moteur en échange standard, le client rend l’ancien – et la TVA est due sur la valeur de cette pièce usagée, bien qu’elle ne soit pas facturée. Il en résulte une facture dont le montant de TVA dépasse 19 % du total hors taxes.
Cette page décrit le droit allemand de la TVA. L’« Altteilesteuer » n’existe qu’en Allemagne et ne s’applique pas aux factures relevant du droit de la TVA d’un autre pays. Elle repose sur le § 3 al. 12 et le § 10 al. 2 phrase 2 de la loi allemande sur la TVA (Umsatzsteuergesetz, UStG) – l’échange avec soulte – ainsi que sur la section 10.5 al. 3 du décret d’application allemand (Umsatzsteuer-Anwendungserlass, UStAE), qui traite de l’échange standard dans le secteur automobile. Cette page montre la représentation technique dans la facture électronique et ne constitue pas un conseil fiscal.
La représentation suit le cas métier 03.06a de la suite
de tests XRechnung publiée par la KoSIT, l’office allemand de
coordination des standards informatiques :
03.06a-INVOICE_uncefact.xml.
Les lignes, les catégories de TVA et les totaux sont identiques ; les
données de base sont celles des autres factures d’exemple de cette
documentation.
De quoi s’agit-il ?
Dans l’échange standard, le garage livre une pièce en état de marche et reçoit deux choses en contrepartie : de l’argent et la pièce usagée à réparer. Du point de vue de la TVA allemande, il s’agit d’un échange avec soulte – la contrepartie de la pièce d’échange comprend donc, outre le paiement, la valeur de la pièce usagée. L’UStAE permet de fixer cette valeur forfaitairement à 10 % du prix d’échange brut (Bruttoaustauschentgelt).
Dans l’exemple, le moteur coûte 1 000 EUR. La pièce usagée vaut donc 100 EUR, ce qui génère 19 EUR de TVA. Le client ne paie pas les 100 EUR eux-mêmes – il les a réglés en remettant la pièce usagée. Les 19 EUR, en revanche, figurent sur la facture.
L’EN 16931 ne prévoit aucun champ dédié. La suite de tests de la KoSIT résout le cas avec deux lignes supplémentaires qui s’annulent en montant hors taxes, mais pas en TVA :
| Ligne | Désignation | Quantité | Total de ligne | Catégorie | TVA |
|---|---|---|---|---|---|
| 1 | Neumotor (moteur neuf) | 1 | 1 000,00 | S, 19 % | 190,00 |
| 2 | Bemessungsgrundlage und Umsatzsteuer auf Altteil (base d’imposition et TVA sur la pièce usagée) | 1 | 100,00 | S, 19 % | 19,00 |
| 3 | Korrektur/Stornierung Bemessungsgrundlage der Umsatzsteuer auf Altteil (correction de cette base d’imposition) | −1 | −100,00 | Z, 0 % | 0,00 |
| 4 | Montage (pose) | 1 | 500,00 | S, 19 % | 95,00 |
| Total | 1 500,00 | 304,00 |
La ligne 2 fait entrer la valeur de la pièce usagée dans la base d’imposition du taux normal. La ligne 3 retire ces mêmes 100 EUR hors taxes, mais porte la catégorie Z à 0 % – la TVA de la ligne 2 est donc conservée. Au final : 1 500 EUR hors taxes, 304 EUR de TVA, 1 804 EUR toutes taxes comprises.
Ce que montre cet exemple
- Une ligne avec une quantité négative et un prix positif dans une facture ordinaire (type 380)
- Deux catégories de TVA dans un même document : S (19 %) et Z (0 %)
- Une ventilation de TVA avec une base d’imposition négative (BT-116)
- Un montant de TVA (BT-110) qui, volontairement, ne correspond pas à 19 % du total hors taxes (BT-109)
Le code ci-dessous construit le document étape par étape. Si vous ne connaissez pas encore FactoorSharp, commencez plutôt par Pour commencer – ici l’accent est mis sur ce qui rend l’Altteilesteuer particulière. Pour chaque numéro BT et BG, vous trouverez l’élément XML correspondant dans la référence Factur-X.
1. En-tête de facture
Une facture commerciale tout à fait ordinaire. La note portant le code
TXD est facultative et ne figure pas dans le modèle de la KoSIT ;
elle explique au destinataire, en une phrase, d’où viennent les 19 EUR
supplémentaires.
FacturXInvoice invoice = FacturXInvoice.CreateInvoice(
invoiceNo: "R87654321012346",
invoiceDate: new DateTime(2025, 10, 1),
currency: CurrencyCodes.EUR);
// BT-3 : 380 = facture commerciale. L'Altteilesteuer n'a pas besoin d'un type de document propre.
invoice.Type = InvoiceType.Invoice;
invoice.Name = "RECHNUNG"; // BT-X-2, EXTENDED uniquement
// Facultatif : le code TXD (information fiscale) explique au lecteur humain
// pourquoi le montant de TVA dépasse 19 % du total hors taxes.
invoice.AddNote("Lieferung im Austauschverfahren. Die Umsatzsteuer auf das Altteil ist "
+ "nach Abschn. 10.5 Abs. 3 UStAE gesondert berechnet "
+ "(Altteilwert 100,00 EUR, 19 % = 19,00 EUR).",
subjectCode: SubjectCodes.TXD);
Dim invoice As FacturXInvoice = FacturXInvoice.CreateInvoice(
invoiceNo:="R87654321012346",
invoiceDate:=New DateTime(2025, 10, 1),
currency:=CurrencyCodes.EUR)
' BT-3 : 380 = facture commerciale. L'Altteilesteuer n'a pas besoin d'un type de document propre.
invoice.Type = InvoiceType.Invoice
invoice.Name = "RECHNUNG" ' BT-X-2, EXTENDED uniquement
' Facultatif : le code TXD (information fiscale) explique au lecteur humain
' pourquoi le montant de TVA dépasse 19 % du total hors taxes.
invoice.AddNote("Lieferung im Austauschverfahren. Die Umsatzsteuer auf das Altteil ist " &
"nach Abschn. 10.5 Abs. 3 UStAE gesondert berechnet " &
"(Altteilwert 100,00 EUR, 19 % = 19,00 EUR).",
subjectCode:=SubjectCodes.TXD)
2. Vendeur et acheteur
Les données de base ne jouent aucun rôle pour l’Altteilesteuer. Elles correspondent à celles de la Facture de marchandises étendue.
// Vendeur (BG-4) : le garage qui livre et pose la pièce d'échange.
invoice.SetSeller(
name: "MUSTERLIEFERANT GMBH",
postcode: "99199",
city: "MUSTERHAUSEN",
street: "BAHNHOFSTRASSE 99",
country: CountryCodes.DE,
id: "549910",
globalID: new GlobalID(GlobalIDSchemeIdentifiers.GLN, "4333741000005"));
invoice.SetSellerContact(
emailAddress: "max.mustermann@musterlieferant.de",
phoneno: "+49 932 431 500");
// BT-31 : numéro de TVA intracommunautaire du vendeur
invoice.AddSellerTaxRegistration("DE123456788", TaxRegistrationSchemeID.VA);
// Acheteur (BG-7) : rend la pièce usagée.
invoice.SetBuyer(
name: "MUSTER-KUNDE GMBH",
postcode: "40235",
city: "KUNDENSTADT",
street: "KUNDENWEG 88",
country: CountryCodes.DE,
id: "009420",
globalID: new GlobalID(GlobalIDSchemeIdentifiers.GLN, "4304171000002"));
invoice.ActualDeliveryDate = new DateTime(2025, 10, 1); // BT-72
' Vendeur (BG-4) : le garage qui livre et pose la pièce d'échange.
invoice.SetSeller(
name:="MUSTERLIEFERANT GMBH",
postcode:="99199",
city:="MUSTERHAUSEN",
street:="BAHNHOFSTRASSE 99",
country:=CountryCodes.DE,
id:="549910",
globalID:=New GlobalID(GlobalIDSchemeIdentifiers.GLN, "4333741000005"))
invoice.SetSellerContact(
emailAddress:="max.mustermann@musterlieferant.de",
phoneno:="+49 932 431 500")
' BT-31 : numéro de TVA intracommunautaire du vendeur
invoice.AddSellerTaxRegistration("DE123456788", TaxRegistrationSchemeID.VA)
' Acheteur (BG-7) : rend la pièce usagée.
invoice.SetBuyer(
name:="MUSTER-KUNDE GMBH",
postcode:="40235",
city:="KUNDENSTADT",
street:="KUNDENWEG 88",
country:=CountryCodes.DE,
id:="009420",
globalID:=New GlobalID(GlobalIDSchemeIdentifiers.GLN, "4304171000002"))
invoice.ActualDeliveryDate = New DateTime(2025, 10, 1) ' BT-72
3. La pièce d’échange
La ligne 1 n’a rien de particulier : une pièce, taux normal.
// Ligne 1 : la pièce d'échange. 1 × 1 000,00 = 1 000,00, 19 %.
invoice.AddTradeLineItem(
name: "Neumotor",
netUnitPrice: 1000.00m,
grossUnitPrice: 1000.00m,
unitCode: QuantityCodes.C62,
billedQuantity: 1m,
lineTotalAmount: 1000.00m,
taxType: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S,
taxPercent: 19m);
' Ligne 1 : la pièce d'échange. 1 × 1 000,00 = 1 000,00, 19 %.
invoice.AddTradeLineItem(
name:="Neumotor",
netUnitPrice:=1000D,
grossUnitPrice:=1000D,
unitCode:=QuantityCodes.C62,
billedQuantity:=1D,
lineTotalAmount:=1000D,
taxType:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S,
taxPercent:=19D)
4. La pièce usagée : deux lignes
C’est ici que se joue la représentation proprement dite. Les deux lignes portent le même prix de 100 EUR ; elles diffèrent par la quantité et la catégorie de TVA.
Le signe se trouve dans la quantité, pas dans le prix. La règle métier BR-27 de l’EN 16931 interdit un prix net négatif (BT-146) ; une quantité négative (BT-129), et donc un total de ligne négatif (BT-131), sont en revanche admis.
// Ligne 2 : la valeur de la pièce usagée (10 % de 1 000,00) entre dans la base
// d'imposition du taux normal : 1 × 100,00 = 100,00, 19 % → 19,00 de TVA.
invoice.AddTradeLineItem(
name: "Bemessungsgrundlage und Umsatzsteuer auf Altteil",
netUnitPrice: 100.00m,
grossUnitPrice: 100.00m,
unitCode: QuantityCodes.C62,
billedQuantity: 1m,
lineTotalAmount: 100.00m,
taxType: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S,
taxPercent: 19m);
// Ligne 3 : retire les 100,00 hors taxes – avec la catégorie Z et 0 %,
// afin que les 19,00 de TVA de la ligne 2 soient conservés.
invoice.AddTradeLineItem(
name: "Korrektur/Stornierung Bemessungsgrundlage der Umsatzsteuer auf Altteil",
netUnitPrice: 100.00m, // positif ! (BR-27)
grossUnitPrice: 100.00m, // positif !
unitCode: QuantityCodes.C62,
billedQuantity: -1m, // négatif !
lineTotalAmount: -100.00m,
taxType: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.Z,
taxPercent: 0m); // indiquer 0 % explicitement (BR-Z-05)
' Ligne 2 : la valeur de la pièce usagée (10 % de 1 000,00) entre dans la base
' d'imposition du taux normal : 1 × 100,00 = 100,00, 19 % → 19,00 de TVA.
invoice.AddTradeLineItem(
name:="Bemessungsgrundlage und Umsatzsteuer auf Altteil",
netUnitPrice:=100D,
grossUnitPrice:=100D,
unitCode:=QuantityCodes.C62,
billedQuantity:=1D,
lineTotalAmount:=100D,
taxType:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S,
taxPercent:=19D)
' Ligne 3 : retire les 100,00 hors taxes – avec la catégorie Z et 0 %,
' afin que les 19,00 de TVA de la ligne 2 soient conservés.
invoice.AddTradeLineItem(
name:="Korrektur/Stornierung Bemessungsgrundlage der Umsatzsteuer auf Altteil",
netUnitPrice:=100D, ' positif ! (BR-27)
grossUnitPrice:=100D, ' positif !
unitCode:=QuantityCodes.C62,
billedQuantity:=-1D, ' négatif !
lineTotalAmount:=-100D,
taxType:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.Z,
taxPercent:=0D) ' indiquer 0 % explicitement (BR-Z-05)
Le taux de TVA de la ligne de correction est transmis explicitement avec
0m. Sans ce paramètre, BT-152 resterait vide – or, pour la
catégorie Z, la règle BR-Z-05 exige précisément la valeur 0.
5. La pose
La ligne 4 est de nouveau ordinaire, cette fois avec l’unité
HUR pour les heures.
// Ligne 4 : la pose. 1 heure × 500,00 = 500,00, 19 %.
invoice.AddTradeLineItem(
name: "Montage",
netUnitPrice: 500.00m,
grossUnitPrice: 500.00m,
unitCode: QuantityCodes.HUR, // HUR = heure
billedQuantity: 1m,
lineTotalAmount: 500.00m,
taxType: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S,
taxPercent: 19m);
' Ligne 4 : la pose. 1 heure × 500,00 = 500,00, 19 %.
invoice.AddTradeLineItem(
name:="Montage",
netUnitPrice:=500D,
grossUnitPrice:=500D,
unitCode:=QuantityCodes.HUR, ' HUR = heure
billedQuantity:=1D,
lineTotalAmount:=500D,
taxType:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S,
taxPercent:=19D)
6. TVA
Le document comporte une ventilation par combinaison de catégorie et de taux (BG-23). La base d’imposition est à chaque fois la somme des lignes de cette catégorie (BR-S-08, BR-Z-08) :
| Catégorie | Base d’imposition (BT-116) | Montant de TVA (BT-117) |
|---|---|---|
| S, 19 % | 1 000,00 + 100,00 + 500,00 = 1 600,00 | 1 600,00 × 19 % = 304,00 |
| Z, 0 % | −100,00 | 0,00 |
// Catégorie S, 19 % : lignes 1, 2 et 4 → 1 000,00 + 100,00 + 500,00 = 1 600,00
invoice.AddApplicableTradeTax(
basisAmount: 1600.00m,
percent: 19m,
taxAmount: 304.00m,
typeCode: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.S);
// Catégorie Z, 0 % : ligne 3 uniquement → base d'imposition −100,00, TVA 0,00
invoice.AddApplicableTradeTax(
basisAmount: -100.00m,
percent: 0m,
taxAmount: 0.00m,
typeCode: TaxTypes.VAT,
categoryCode: TaxCategoryCodes.Z);
' Catégorie S, 19 % : lignes 1, 2 et 4 → 1 000,00 + 100,00 + 500,00 = 1 600,00
invoice.AddApplicableTradeTax(
basisAmount:=1600D,
percent:=19D,
taxAmount:=304D,
typeCode:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.S)
' Catégorie Z, 0 % : ligne 3 uniquement → base d'imposition −100,00, TVA 0,00
invoice.AddApplicableTradeTax(
basisAmount:=-100D,
percent:=0D,
taxAmount:=0D,
typeCode:=TaxTypes.VAT,
categoryCode:=TaxCategoryCodes.Z)
Dans le XML, les deux ventilations se suivent. La base d’imposition négative n’est pas une erreur, mais le cœur de la représentation :
<ram:ApplicableTradeTax>
<ram:CalculatedAmount>304.00</ram:CalculatedAmount>
<ram:TypeCode>VAT</ram:TypeCode>
<ram:BasisAmount>1600.00</ram:BasisAmount>
<ram:CategoryCode>S</ram:CategoryCode>
<ram:RateApplicablePercent>19.00</ram:RateApplicablePercent>
</ram:ApplicableTradeTax>
<ram:ApplicableTradeTax>
<ram:CalculatedAmount>0.00</ram:CalculatedAmount>
<ram:TypeCode>VAT</ram:TypeCode>
<ram:BasisAmount>-100.00</ram:BasisAmount>
<ram:CategoryCode>Z</ram:CategoryCode>
<ram:RateApplicablePercent>0.00</ram:RateApplicablePercent>
</ram:ApplicableTradeTax>
7. Conditions de paiement
Toute facture dont le montant à payer est positif doit indiquer l’échéance (BR-CO-25) – sous forme de date (BT-9) ou de texte (BT-20). Ici, les deux sont renseignés.
// BT-20 et BT-9 : conditions de paiement en texte libre plus la date d'échéance.
invoice.AddTradePaymentTerms(
description: "Zahlbar innerhalb 30 Tagen ohne Abzug.",
dueDate: new DateTime(2025, 10, 31));
' BT-20 et BT-9 : conditions de paiement en texte libre plus la date d'échéance.
invoice.AddTradePaymentTerms(
description:="Zahlbar innerhalb 30 Tagen ohne Abzug.",
dueDate:=New DateTime(2025, 10, 31))
8. Totaux
Les totaux découlent des lignes et des deux ventilations :
| Code BT | Paramètre | Calcul |
|---|---|---|
| BT-106 | lineTotalAmount |
1 000,00 + 100,00 − 100,00 + 500,00 = 1 500,00 |
| BT-109 | taxBasisAmount |
1 600,00 + (−100,00) = 1 500,00 |
| BT-110 | taxTotalAmount |
304,00 + 0,00 = 304,00 |
| BT-112 | grandTotalAmount |
1 500,00 + 304,00 = 1 804,00 |
| BT-115 | duePayableAmount |
1 804,00 |
19 % de 1 500,00 feraient 285,00 – le document indique 304,00. La différence de 19,00 est précisément l’Altteilesteuer. La validation ne la signale pas : l’EN 16931 contrôle le montant de TVA par ventilation (BR-CO-17) et la somme des ventilations (BR-CO-14), pas le rapport entre BT-110 et BT-109.
invoice.SetTotals(
lineTotalAmount: 1500.00m, // BT-106
taxBasisAmount: 1500.00m, // BT-109
taxTotalAmount: 304.00m, // BT-110 – 19,00 de plus que 19 % de 1 500,00
grandTotalAmount: 1804.00m, // BT-112
duePayableAmount: 1804.00m); // BT-115
invoice.SetTotals(
lineTotalAmount:=1500D, ' BT-106
taxBasisAmount:=1500D, ' BT-109
taxTotalAmount:=304D, ' BT-110 – 19,00 de plus que 19 % de 1 500,00
grandTotalAmount:=1804D, ' BT-112
duePayableAmount:=1804D) ' BT-115
9. Enregistrer et valider
invoice.Save("Altteilesteuer.xml",
ZUGFeRDVersion.Version25,
Profile.Extended,
ZUGFeRDFormats.CII);
invoice.Save("Altteilesteuer.xml",
ZUGFeRDVersion.Version25,
Profile.Extended,
ZUGFeRDFormats.CII)
La représentation ne dépend pas du profil EXTENDED. Les lignes, la TVA et les totaux peuvent être enregistrés tels quels en BASIC, COMFORT (EN 16931) ou XRechnung, en CII comme en UBL. Pour XRechnung s’ajoutent les mentions obligatoires habituelles, notamment la Leitweg-ID (BT-10), le nom de l’interlocuteur (BT-41), les adresses électroniques du vendeur et de l’acheteur (BT-34, BT-49) et les informations de paiement (BG-16).
Quant à savoir si le document respecte le jeu de règles, c’est le validateur qui répond – dans le navigateur ou directement depuis votre code. Une version lisible par un humain est produite par le visualiseur. Les deux, ainsi que leur documentation détaillée, se trouvent dans l’ espace client ; comment y accéder est expliqué sous Pour commencer.