Créer des factures ZUGFeRD et XRechnung avec Delphi – liaison tardive et anticipée (à partir de Delphi 5)

Membres du SDK
ActualDeliveryDate_102 AddPaymentMeans() AddProduct_Comfort() AddTradePaymentTerms() AddTradeTax() BuyerAddressLine1 BuyerCity BuyerCountry BuyerName BuyerPostCode
Termes métier
BT-1 Invoice number BT-2 Invoice issue date BT-3 Invoice type code BT-5 Invoice currency code BT-31 Seller VAT identifier BT-72 Actual delivery date BG-25 Invoice line BG-23 VAT breakdown
Exemples de code
Ligne de commande, Delphi
Publié le

En bref : les applications Delphi – y compris les plus anciennes écrites en Delphi 5 ou Delphi 7 – créent des factures électroniques conformes à la EN 16931 (ZUGFeRD 2.x, Factur-X, XRechnung) via l'interface COM du SDK Aloaha ZUGFeRD. Il n'est pas nécessaire de changer de version de Delphi. Deux voies sont possibles : la liaison tardive avec CreateOleObject('Aloaha_ZUGFeRD.XML'), sans importation, ou la liaison anticipée via la bibliothèque de types fournie Aloaha_ZUGFeRD.tlb. Votre programme crée la facture PDF comme auparavant ; le SDK écrit le XML et l'intègre dans le PDF (facture ZUGFeRD hybride, PDF/A-3).

Liaison tardive ou anticipée ?

Liaison tardiveLiaison anticipée
Créer l'objetCreateOleObject('Aloaha_ZUGFeRD.XML')CoXML.Create depuis l'unité importée
Importer la bibliothèque de typesnonoui, une fois (Aloaha_ZUGFeRD.tlb)
Complétion de code dans l'IDEnonoui
Contrôle des noms et des paramètresà l'exécutionà la compilation
Paramètres facultatifsles omettre en fin de listesous Delphi 5, tous les transmettre
Enregistrement de la DLLnécessaire dans les deux cas – effectué par le programme d'installation du SDK

Pour un démarrage rapide et pour de petites modifications de programmes existants, la liaison tardive est la voie la plus simple. Si vous programmez beaucoup avec le SDK, la liaison anticipée est rentable.

Prérequis

  • Windows avec .NET Framework 4.7.2 ou ultérieur.
  • SDK Aloaha ZUGFeRD depuis la page de téléchargement. Le programme d'installation du SDK (MSI) enregistre l'interface COM pour 32 et 64 bits.
  • Distribution manuelle sans programme d'installation : enregistrez la DLL avec le Regasm de l'architecture correspondante, en tant qu'administrateur. Delphi 5 à Delphi XE produisent des programmes 32 bits ; utilisez pour ceux-ci le Regasm de C:\Windows\Microsoft.NET\Framework\v4.0.30319\ (et non Framework64) :
    regasm /codebase Aloaha_ZUGFeRD.dll
    Les fichiers nécessaires sont décrits dans Register libraries for Delphi.
  • Autotest COM : le paquet zugferdbinaries.zip contient le script zugferd_com_test.vbs. Il vérifie l'enregistrement et l'architecture et crée l'objet à titre de test ; pour le test 32 bits sur un Windows 64 bits, lancez-le avec C:\Windows\SysWOW64\cscript.exe.
  • Clé de licence : vous obtenez une licence d'essai gratuite et complète par e-mail à info@aloaha.com.

Étape par étape

  1. Installer le SDK (ou enregistrer la DLL, voir ci-dessus).
  2. Créer l'objet, définir la clé de licence et le profil.
  3. Renseigner l'en-tête de facture, le vendeur, l'acheteur et les données de paiement.
  4. Ajouter les lignes dans une boucle (AddProduct_Comfort).
  5. Définir la ventilation de la TVA par taux (AddTradeTax) et les totaux (WriteTotals).
  6. Enregistrer le XML (SaveXML) et l'intégrer dans la facture PDF existante (MergeXMLIntoPDF).
  7. Contrôler la première facture avec un validateur – voir Comment contrôler ma XRechnung avant l'envoi ?

Toujours transmettre les montants avec un point décimal

Le SDK attend des montants sous forme de texte avec un point comme séparateur décimal (59.50). Avec des paramètres régionaux Windows français ou allemands, Delphi écrirait une virgule. Les deux exemples utilisent cette fonction d'aide (Betrag = montant) :

// Delphi 5 à 2010
function Betrag(Wert: Double): string;
begin
  Result := StringReplace(FormatFloat('0.00', Wert), DecimalSeparator, '.', []);
end;

Variante 1 : liaison tardive avec CreateOleObject

uses ComObj;

procedure ErzeugeERechnung_LateBinding;
var
  alo: OleVariant;
begin
  alo := CreateOleObject('Aloaha_ZUGFeRD.XML');
  alo.Licensekey := 'VOTRE-CLE-DE-LICENCE';
  alo.ZUGFeRD_Profile := 1;                       // 0 = BASIC, 1 = EN 16931 (COMFORT), 2 = EXTENDED

  // en-tête : numéro (BT-1), texte, date (BT-2), format de date, nom, langue, devise (BT-5)
  alo.CreateInvoiceObject('RE-2027-0001', 'Invoice as per order',
                          '20270104', '102', '', 'DE', 'EUR');
  alo.Document_TypeINT := 380;                    // BT-3 : 380 = facture, 381 = avoir
  alo.ActualDeliveryDate_102 := '20270104';       // BT-72 : date de livraison

  // vendeur avec numéro de TVA (BT-31)
  alo.WriteSeller('Muster GmbH', '20095', 'Hamburg', 'Hauptstr. 1', '', 'DE', 'DE123456789');

  // acheteur
  alo.BuyerName := 'Kunde AG';
  alo.BuyerAddressLine1 := 'Marktplatz 5';
  alo.BuyerPostCode := '80331';
  alo.BuyerCity := 'München';
  alo.BuyerCountry := 'DE';

  // paiement : 58 = virement SEPA (BT-81)
  alo.AddPaymentMeans(58, 'SEPA credit transfer', 'DE02120300000000202051',
                      'Muster GmbH', '', 'BYLADEM1001');
  alo.AddTradePaymentTerms('Payable within 30 days net');

  // ligne (BG-25) : 10 pièces à 5,00 EUR, 19 %
  alo.AddProduct_Comfort(1, '', 'ART-100', '10.00', 'C62', 'Paper A4', '500 sheets',
                         '', 'EUR', '', 'C62', False, '', '', '', Betrag(5.00), 'EUR', '1.00',
                         'C62', 'VAT', 'S', '19.00', '', Betrag(50.00), 'EUR', '', '');

  // ventilation TVA (BG-23) : taxe, devise, base, devise, type, catégorie, taux
  alo.AddTradeTax(Betrag(9.50), 'EUR', Betrag(50.00), 'EUR', 'VAT', 'S', '19.00');

  // totaux (BG-22) : lignes, total TTC, montant dû, net, taxe
  alo.WriteTotals(Betrag(50.00), Betrag(59.50), Betrag(59.50), Betrag(50.00), Betrag(9.50));

  // enregistrer le XML et l'intégrer dans la facture PDF existante
  if alo.SaveXML('C:\Invoices\RE-2027-0001.xml') then
    alo.MergeXMLIntoPDF('C:\Invoices\RE-2027-0001.xml',
                        'C:\Invoices\RE-2027-0001.pdf',
                        'C:\Invoices\RE-2027-0001_zugferd.pdf',
                        True, '', 'VOTRE-CLE-DE-LICENCE')
  else
    ShowMessage(alo.Last_Error);
end;

En pratique, remplacez les valeurs fixes par les champs de votre table de factures et appelez AddProduct_Comfort dans une boucle sur les lignes. Pour une XRechnung destinée aux administrations allemandes, définissez en outre alo.isKOSIT := True et alo.LeitwegID.

Variante 2 : liaison anticipée via la bibliothèque de types

La bibliothèque de types Aloaha_ZUGFeRD.tlb est fournie avec le SDK. Vous n'avez pas à la créer vous-même ; si nécessaire, utilisez regasm /codebase Aloaha_ZUGFeRD.dll /tlb:Aloaha_ZUGFeRD.tlb.

  1. Sous Delphi 5 : Project > Import Type Library > choisir « Aloaha ZUGFeRD Toolkit » > Create Unit. Delphi crée Aloaha_ZUGFeRD_TLB.pas ainsi que mscorlib_TLB.pas.
  2. Si le compilateur signale des erreurs concernant _SetIUnknownProp dans l'unité générée, supprimez ces lignes.
  3. Ajoutez l'unité à la clause uses. La classe de facture s'appelle XML, son interface _XML ; elle est créée avec CoXML.Create. Vérifiez les noms dans l'unité générée.

Delphi 5 ne reprend pas de manière fiable les valeurs par défaut des paramètres facultatifs depuis la bibliothèque de types. Transmettez donc tous les paramètres :

uses ComObj, Aloaha_ZUGFeRD_TLB;

procedure ErzeugeERechnung_EarlyBinding;
var
  alo: _XML;
  Standard: TOleEnum;
begin
  alo := CoXML.Create;
  alo.Licensekey := 'VOTRE-CLE-DE-LICENCE';
  alo.ZUGFeRD_Profile := 1;                       // 1 = EN 16931 (COMFORT)

  alo.CreateInvoiceObject('RE-2027-0001', 'Invoice as per order',
                          '20270104', '102', '', 'DE', 'EUR');
  alo.Document_TypeINT := 380;
  alo.ActualDeliveryDate_102 := '20270104';

  // vendeur : nom, code postal, ville, rue, rue 2, pays, n° TVA, rue 3
  alo.WriteSeller('Muster GmbH', '20095', 'Hamburg', 'Hauptstr. 1', '', 'DE', 'DE123456789', '');

  alo.BuyerName := 'Kunde AG';
  alo.BuyerAddressLine1 := 'Marktplatz 5';
  alo.BuyerPostCode := '80331';
  alo.BuyerCity := 'München';
  alo.BuyerCountry := 'DE';

  // paiement : type, texte, IBAN, titulaire, ID de compte, BIC, code banque, banque,
  //            prélèvement, mandat, ID créancier
  alo.AddPaymentMeans(58, 'SEPA credit transfer', 'DE02120300000000202051',
                      'Muster GmbH', '', 'BYLADEM1001', '', '', False, '', '');
  alo.AddTradePaymentTerms('Payable within 30 days net', '', '', '', '', '', '', '', '');

  alo.AddProduct_Comfort(1, '', 'ART-100', '10.00', 'C62', 'Paper A4', '500 sheets',
                         '', 'EUR', '', 'C62', False, '', '', '', Betrag(5.00), 'EUR', '1.00',
                         'C62', 'VAT', 'S', '19.00', '', Betrag(50.00), 'EUR', '', '');

  // 7 paramètres obligatoires et 5 facultatifs
  alo.AddTradeTax(Betrag(9.50), 'EUR', Betrag(50.00), 'EUR', 'VAT', 'S', '19.00',
                  '', '', '', '', '');

  // lignes, total TTC, montant dû, net, taxe, remises, payé, arrondi, frais
  alo.WriteTotals(Betrag(50.00), Betrag(59.50), Betrag(59.50), Betrag(50.00), Betrag(9.50),
                  '0.00', '0.00', '0.00', '0.00');

  Standard := 2;                                  // 2 = ZUGFeRD 2.x / Factur-X / XRechnung
  if alo.SaveXML('C:\Invoices\RE-2027-0001.xml', 'VOTRE-CLE-DE-LICENCE', Standard) then
    alo.MergeXMLIntoPDF('C:\Invoices\RE-2027-0001.xml',
                        'C:\Invoices\RE-2027-0001.pdf',
                        'C:\Invoices\RE-2027-0001_zugferd.pdf',
                        True, '', 'VOTRE-CLE-DE-LICENCE', '', '', '', '')
  else
    ShowMessage(alo.Last_Error);
end;

L'ordre des méthodes dans l'interface COM n'a pas changé depuis la version 10 ; les nouvelles fonctions ne sont ajoutées qu'à la fin. Une unité importée une fois continue donc de fonctionner avec les versions plus récentes du SDK.

Les méthodes surchargées portent d'autres noms en COM

COM ne connaît pas la surcharge. Si une méthode existe plusieurs fois en .NET, la deuxième variante reçoit le suffixe _2 dans Delphi. Points importants pour Delphi :

Nom dans DelphiParamètres
WriteTotalsmontants sous forme de texte ('59.50')
WriteTotals_2montants en Double
AddProduct_Comfortnuméro de ligne sous forme de nombre
AddProduct_Comfort_2numéro de ligne sous forme de texte (p. ex. '1.1')
AddPaymentMeansmoyen de paiement sous forme de nombre (UNTDID 4461, p. ex. 58)
AddPaymentMeans_2moyen de paiement sous forme de texte ('58')

Dans Delphi, transmettez les énumérations comme le profil ou le moyen de paiement sous forme de nombres. Pour le type de facture (BT-3), il existe une propriété dédiée Document_TypeINT, voir Créer une facture et le type de facture (BT-3) (en allemand).

Versions plus récentes de Delphi

  • Delphi 2009 et ultérieur : string est en Unicode ; les caractères accentués parviennent au SDK sans détour. Le code reste identique.
  • À partir de Delphi XE : la variable globale DecimalSeparator est obsolète. Utilisez TFormatSettings :
    function Betrag(Wert: Double): string;
    var
      FS: TFormatSettings;
    begin
      FS := TFormatSettings.Create;
      FS.DecimalSeparator := '.';
      Result := FormatFloat('0.00', Wert, FS);
    end;
  • Programmes 64 bits (à partir de Delphi XE2) : la DLL doit aussi être enregistrée pour 64 bits. Le programme d'installation du SDK fait les deux ; pour un enregistrement manuel, utilisez le Regasm de Framework64.

Erreurs fréquentes

MessageCauseSolution
« Invalid class string »La classe COM n'est pas enregistrée.Exécuter le programme d'installation du SDK ou regasm /codebase en tant qu'administrateur.
« Class not registered »Enregistrée uniquement pour l'autre architecture, p. ex. 64 bits pour un programme 32 bits.Enregistrer avec le Regasm correspondant (Framework pour 32 bits).
« Unknown name »Faute de frappe, ou la variante surchargée porte un autre nom (_2).Vérifier le nom ; la liaison anticipée signale ces erreurs dès la compilation.
« Type mismatch »Du texte là où le SDK attend un nombre (ou inversement).Vérifier le type de paramètre de la variante, voir le tableau ci-dessus.
Validateur : BR-CO-10, BR-S-08, BR-CO-17Les totaux ne correspondent pas aux lignes ou à la base d'imposition.Base d'imposition = net après remises, voir BR-S-08, BR-S-09 et BR-CO-17.

Si SaveXML renvoie False, la cause figure dans Last_Error.

Alternative sans COM : zugcmd

Si le code Delphi doit rester aussi inchangé que possible, votre programme écrit les données de facture dans des fichiers CSV et appelle l'outil en ligne de commande zugcmd, par exemple via ShellExecute. zugcmd en produit une facture ZUGFeRD, Factur-X ou XRechnung et intègre le XML dans votre PDF.

Étape par étape, avec des fichiers d'exemple testés, l'appel depuis votre propre application et les noms de fichiers : Créer des factures électroniques via CSV et zugcmd depuis votre application.

Tous les appels en détail, avec remises et plusieurs taux de TVA : démarrage rapide avec VB.NET – les méthodes et paramètres sont les mêmes en Delphi. Le même guide pour Visual Basic : Créer ZUGFeRD et XRechnung avec VB6, VB5 et VBA.

Questions fréquentes

Puis-je créer des factures ZUGFeRD avec Delphi 5 ?

Oui. Le SDK Aloaha ZUGFeRD dispose d'une interface COM que Delphi 5 utilise via CreateOleObject('Aloaha_ZUGFeRD.XML') ou via la bibliothèque de types fournie. Une version plus récente de Delphi n'est pas nécessaire.

Dois-je importer la bibliothèque de types ?

Non, pas pour la liaison tardive. L'importation (liaison anticipée) apporte la complétion de code et des contrôles à la compilation. La DLL doit être enregistrée dans les deux cas ; le programme d'installation du SDK s'en charge.

Cela fonctionne-t-il aussi avec Delphi 7, XE ou Delphi 12 ?

Oui, la voie COM est la même. À partir de Delphi XE, utilisez TFormatSettings pour le point décimal ; les programmes 64 bits nécessitent l'enregistrement 64 bits.

Puis-je continuer à utiliser ma facture PDF existante produite par Delphi ?

Oui. MergeXMLIntoPDF intègre le XML dans votre facture PDF existante et en fait une facture ZUGFeRD hybride (PDF/A-3).

Puis-je aussi créer une XRechnung pure avec Delphi ?

Oui. Définissez isKOSIT := True et le Leitweg-ID, puis enregistrez uniquement le XML avec SaveXML.

Pourquoi une méthode s'appelle-t-elle soudain WriteTotals_2 dans Delphi ?

COM ne connaît pas la surcharge. La deuxième variante .NET d'une méthode reçoit le suffixe _2. WriteTotals attend les montants sous forme de texte, WriteTotals_2 en Double.

Termes métier dans cet article : BT-1 BT-2 BT-3 BT-5 BT-31 BT-72 BG-25 BG-23 BG-22 BT-81 BT-106 BT-109 BT-110 BT-112 BT-115 BT-116