Distribution automatisée des factures / notes de crédit reçues via Peppol

0000003285     -      21/10/2025

Les processus décrits ici permettent d'automatiser la distribution des messages provenant du réseau Peppol, contenant une facture ou une note de crédit. Lors de la réception d'un message via le PeppolBoxHelper, celui-ci sera décodé et distribué, en fonction de son contenu, à tel ou tel département. Cette distribution se fait via une action du CRM de Mercator. Cette action contient un bouton permettant l'injection de la facture / note de crédit, soit en gestion commerciale, soit en comptabilité. Dans les modèles proposés ici, l'action est conçue comme une tâche.

Deux actions sont créées :

  • Document Peppol à importer : pour la gestion commerciale (par exemple, les factures de marchandises)
  • Ecriture Peppol à importer : pour la comptabilité (par exemple, les factures de frais généraux).

Les modèles d'action sont disponibles dans le fichier zip ci-dessous. Pour installer ces actions :

 

Une tâche en mode console télécharge à intervalle régulier les messages. La PeppolBox n'est donc plus utilisée. La méthode à placer dans le customizer Main est reprise ci-dessous. Chaque message correctement identifié fera l'objet de la génération d'une de ces deux actions.

Zoom
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Linq;
using MercatorApi;
using MercatorExtensions;
using MercatorUi;
using MercatorDatabase;

// <CompileWithRoslyn />

namespace Main
{
    public class Customizer
    {
        public void DownloadAndDispatchPeppolMessages()
        {
            Globals.MercatorTasksToMain.Log("Instanciation du PeppolBoxHelper");
            using (MercatorUi.Forms.Other.OtherClasses.PeppolBoxHelper peppolBoxHelper = new MercatorUi.Forms.Other.OtherClasses.PeppolBoxHelper())
            {
                if (peppolBoxHelper.DataTable == null)
                    Globals.MercatorTasksToMain.Log("Erreur lors de l'initialisation du PeppolBoxHelper : " + Api.LastError, isError: true);
                else if (!peppolBoxHelper.Download())
                    Globals.MercatorTasksToMain.Log("Erreur lors de téléchargement des messages : " + Api.LastError, isError: true);
                else
                {
                    Globals.MercatorTasksToMain.Log("Message(s) téléchargé(s) : " + peppolBoxHelper.Messages.Count(m => m.IsNew));
                    int nActions = 0;
                    foreach (MercatorUi.Forms.Other.OtherClasses.PeppolBoxHelper.PeppolMessage message in peppolBoxHelper.Messages.Where(m => m.IsNew && m.IsInvoiceOrCreditNote))
                    {
                        MercatorPeppol.ReceivedDoc.ParseContentRet parsedContent = message.ReceivedDoc.ParseContent(Globals.Langue, MercatorPeppol.ReceivedDoc.ParseContentEnum.WithSupplierName | MercatorPeppol.ReceivedDoc.ParseContentEnum.WithPdf);
                        if (!string.IsNullOrWhiteSpace(parsedContent.Error))
                        {
                            Globals.MercatorTasksToMain.Log(string.Format("Parsing message {0} : {1}", message.Id, parsedContent.Error), isError: true);
                            continue;
                        }

                        string id_actempl = null;
                        // ici on met les tests qui permettent de déterminer en fonction du contenu du message le type d'action du CRM qui doit être utilisée
                        if (...)
                        {
                            id_actempl = ".A-0RFCEXW"; // id_actempl gescom
                        }
                        else if (...)
                        {
                            id_actempl = ".A-KC6V639"; // id_actempl compta
                        }
                        if (id_actempl == null)
                            continue; // ce message n'est pas dispatché

                        using (MercatorUi.Engine.Crm.ActionEngine actionEngine = MercatorUi.Engine.Crm.ActionEngine.InitNew(MercatorUi.Sig._SigEnum._none, null, id_actempl))
                        {
                            if (actionEngine == null)
                            {
                                Globals.MercatorTasksToMain.Log("ActionEngine.InitNew : " + actionEngine.LastError, isError: true);
                                continue;
                            }
                            if (actionEngine.ReadOnly)
                            {
                                Globals.MercatorTasksToMain.Log("ActionEngine.InitNew : engine is ReadOnly!");
                                continue;
                            }
                            actionEngine.ActionsRecord.OBJET = (message.ReceivedDoc.ChangeType == "INVOICE_RECEIVED" ? "Facture" : "Note de crédit") + " Peppol de " + parsedContent.SupplierName;
                            actionEngine.ActionsRecord.ENTRYID = message.Id.ToString();
                            actionEngine.ActionsRecord.MOMENT_2 = message.Moment;
                            actionEngine.ActionsRecord.MOMENT_1 = message.Moment.AddDays(5); // échéance pour l'action portée à 5 jours

                            if (!actionEngine.Save())
                            {
                                Globals.MercatorTasksToMain.Log(string.Format("Message {0} : erreur lors de la sauvegarde de l'action : {1}", message.Id, actionEngine.LastError), isError: true);
                                continue;
                            }
                            else if (parsedContent.Pdf == null)
                            {
                                Globals.MercatorTasksToMain.Log(string.Format("Message {0} : pas de PDF dans le message", message.Id), isError: true);
                            }
                            else
                            {
                                if (!Api.BytesToSqlFile(parsedContent.Pdf, Api.AddBS(actionEngine.SqlFileViewDefaultDirectory) + "PeppolDoc.pdf"))
                                    Globals.MercatorTasksToMain.Log(string.Format("Message {0} : impossible de joindre le PDF à l'action", message.Id), isError: true);
                            }
                               
                            nActions++;
                        }
                    }
                    Globals.MercatorTasksToMain.Log("Action(s) créée(s) : " + nActions);
                }
            }
        }
    }
}

Dans le fichier ini, cette ligne sera spécifiée :

Task1= DownloadAndDispatchPeppolMessages

Les tests permettant de déterminer l'action à générer doivent être adaptés selon des critères à concevoir. Ce code, par exemple, permet d'obtenir le numéro de TVA du fournisseur qui peut être utile pour son identification préalable à l'attribution de l'action :

Zoom
XmlNode nodeNumTva = parsedContent.XmlSelectNodes("cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID").FirstOrDefault();
string numTva = nodeNumTva?.InnerText;

 

Les actions générées apparaissent dans la liste des tâches des utilisateurs concernés. Le cas échéant, la liste des destinataires peut être modifiée en agissant par code sur les droits de l'action. Chaque action contient un PDF permettant de prévisualiser la facture / note de crédit. Elle contient aussi un bouton permettant de déclencher l'import proprement dit.

Par exemple, pour l'import en gestion commerciale, le code de ce bouton est :

Zoom
public static void Exec(MercatorUi.MovableControls.MovableButton clickedButton)
{
    // enter your customized code here
    MercatorUi.Forms.Action.ActionForm actionForm = (MercatorUi.Forms.Action.ActionForm)clickedButton.Form;
    MercatorUi.Forms.Other.OtherClasses.PeppolBoxHelper.PeppolMessage peppolMessage =
        MercatorUi.Forms.Other.OtherClasses.PeppolBoxHelper.PeppolMessageFromAction(actionForm.ActionEngine);
    actionForm.Close();
    peppolMessage?.StartImportIntoBilling(new MercatorUi.Engine.Crm.Tools.ActionDescriptor(actionForm.ActionEngine));
}

Pour l'import en comptabilité, la dernière ligne doit simplement être modifiée : StartImportIntoAccounting

Une fois l'écriture ou le document de la gestion commerciale généré et sauvegardé, la tâche est automatiquement marquée comme "faite".

Il faut noter que ce paramétrage ne supprime pas les messages de la PeppolBox. Les messages sont supprimés après importation et sauvegarde, si l'utilisateur répond par l'affirmative à la question "Supprimer ce document de la PeppolBox ?". Comme lors d'une importation habituelle depuis la PeppolBox.

 


 

Le paramétrage ci-dessous montre comment donner la possibilité à un utilisateur de créer ces actions manuellement depuis la PeppolBox. Cela permet, par exemple, à un administrateur de distribuer les messages qui n'ont pas pu être traités automatiquement par la tâche illustrée ci-dessus. Ceci se fait en codeless via le paramétrage de grilles sans code.

Il faut se rendre dans "Outils / Paramètres / Customizers de Grilles" et sélectionner dans le déroulant PeppolBox. 

  • Y cocher AutoAddCustomColumns
  • Placer ce StringUpdater : 
    • à gauche : as isnew
    • à droite : as isnew,'CreateActionFromPeppolMessage' as UserDefinedButtonColumnCustom
  • Ensuite, on ajoute une règle de colonne dans la propriété CustomRules :
    • Name : UserDefinedButtonColumnCustom
    • Width : 50
    • ButtonText : Action

Et enfin placer ce code dans le customizer PeppolBox :

Zoom
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Linq;
using MercatorApi;
using MercatorExtensions;
using MercatorUi;
using MercatorDatabase;
using System.Xml;

// <CompileWithRoslyn />

namespace PeppolBox
{
    public class Customizer
    {
        public void CreateActionFromPeppolMessage(MercatorUi.GridPro.DataGridViewXPro grid, System.Windows.Forms.DataGridViewCellEventArgs e)
        {
            MercatorUi.Forms.Other.PeppolBoxForm peppolBoxForm = (MercatorUi.Forms.Other.PeppolBoxForm)grid.FindForm();
            Guid guid = (Guid)grid.Rows[e.RowIndex].Cells["id"].Value;

            List<bool> l = Api.Zselect<bool>(MercatorUi.Globals.RepData, "if exists(select * from ACTIONS where entryid=@id) select cast(1 as bit) as ret else select cast(0 as bit) as ret", new MercatorSqlParam("@id", guid.ToString(), SqlDbType.Char));
            if (l == null)
                return;
            if (l[0])
            {
                MercatorUi.Dialogs.Stop("Ce message est déjà inclus dans une action du CRM !");
                return;
            }

            MercatorUi.Forms.Other.OtherClasses.PeppolBoxHelper.PeppolMessage message = peppolBoxForm.PeppolBoxHelper.Messages.First(m => m.Id == guid);
            if (!message.IsInvoiceOrCreditNote)
                return;
            MercatorPeppol.ReceivedDoc.ParseContentRet parsedContent = message.ReceivedDoc.ParseContent(Globals.Langue, MercatorPeppol.ReceivedDoc.ParseContentEnum.WithSupplierName | MercatorPeppol.ReceivedDoc.ParseContentEnum.WithPdf);
            if (!string.IsNullOrWhiteSpace(parsedContent.Error))
            {
                MercatorUi.Dialogs.Stop(string.Format("Parsing message {0} : {1}", message.Id, parsedContent.Error));
                return;
            }

            int n = MercatorUi.Dialogs.Answer3Buttons("Créer une action du CRM avec ce message ?", "Gescom", "Compta", Api.Iif_langue(MercatorUi.Globals.Langue, IifLangueEnum.Cancel));
            if (n == 3)
                return; // Cancel

            MercatorUi.Engine.Crm.Tools.ActionDescriptor actionCreated;
            string id_actempl = n == 1 ? ".A-0RFCEXW" : ".A-KC6V639";
            using (MercatorUi.Engine.Crm.ActionEngine actionEngine = MercatorUi.Engine.Crm.ActionEngine.InitNew(MercatorUi.Sig._SigEnum._none, null, id_actempl))
            {
                if (actionEngine == null)
                {
                    MercatorUi.Dialogs.Stop("ActionEngine.InitNew : " + actionEngine.LastError);
                    return;
                }
                if (actionEngine.ReadOnly)
                {
                    MercatorUi.Dialogs.Stop("ActionEngine.InitNew : engine is ReadOnly!");
                    return;
                }

                actionEngine.ActionsRecord.OBJET = (message.ReceivedDoc.ChangeType == "INVOICE_RECEIVED" ? "Facture" : "Note de crédit") + " Peppol de " + parsedContent.SupplierName;
                actionEngine.ActionsRecord.ENTRYID = message.Id.ToString();
                actionEngine.ActionsRecord.MOMENT_2 = message.Moment;
                actionEngine.ActionsRecord.MOMENT_1 = message.Moment.AddDays(5);

                if (!actionEngine.Save())
                {
                    MercatorUi.Dialogs.Stop(string.Format("Erreur lors de la sauvegarde de l'action : {0}", actionEngine.LastError));
                    return;
                }
                else if (parsedContent.Pdf == null)
                {
                    MercatorUi.Dialogs.Stop("Pas de PDF dans le message");
                }
                else
                {
                    if (!Api.BytesToSqlFile(parsedContent.Pdf, Api.AddBS(actionEngine.SqlFileViewDefaultDirectory) + "PeppolDoc.pdf"))
                        MercatorUi.Dialogs.Stop("Impossible de joindre le PDF à l'action");
                }
                actionCreated = new MercatorUi.Engine.Crm.Tools.ActionDescriptor(actionEngine);
            }
            MercatorUi.Globals.Main.ShowActionExisting(actionCreated.Id, actionCreated.Module, actionCreated.IdSig);
        }
    }
}


A télécharger : 0000003285.zip (13 Kb - 25/03/2025)


Cookies fonctionnels : Cookies nécessaires à l'utilisation du site et cookies de préférence. Ils ne contiennent aucune donnée à caractère personnel. (En savoir plus)

Cookies statistiques : Captation de statistiques liées aux comportements des internautes. (En savoir plus)

Cookies marketing : Pour effectuer le suivi des visiteurs au travers des sites web, à des fins publicitaires. (En savoir plus)