Aller au contenu

Comment Développer dans Smart Processing (Documentation Développeur)

Statut : 01.08.2026 • Temps de lecture : ~10 minutes

Smart Processing fournit des points d'extension où vous pouvez implémenter une logique personnalisée pour démarrer vos propres processus ou adapter le traitement aux exigences des clients. Ce document explique le concept clé du développement : travailler à travers la session active et déclencher des codeunits personnalisés dans le processus correspondant en utilisant les données de session.

1. Concept de Base : Travailler à Travers la Session de Traitement Active

Smart Processing exécute une “session de brouillon active” pendant qu'un document est traité. Pendant ce temps, l'état pertinent est maintenu de manière centrale (contexte du document, contexte du modèle, lignes correspondantes, écarts et données intermédiaires utilisées par l'interface utilisateur et le pipeline de traitement).

En tant que développeur, l'approche recommandée est :

  • Lire le contexte de traitement actuel à partir de la session.
  • Effectuer votre logique en utilisant le contexte et les références fournis.
  • Écrire les résultats/l'état dans la session lorsque le flux de travail en a besoin.
  • Compter sur la plateforme pour maintenir l'interface utilisateur et le traitement alignés grâce à l'architecture de session.

Cela rend les personnalisations stables et évite un état incohérent entre ce que les utilisateurs voient et ce que votre logique a changé.


2. L'Objet Central : “SIM_DI Process Session SI”

Le codeunit “SIM_DI Process Session SI” est le point d'entrée central pour accéder et manipuler la session Smart Processing actuellement active. Il agit comme un hub pour obtenir des données d'exécution pertinentes (document entrant, modèle de processus, tampons de ligne, métadonnées d'écart) et pour communiquer les résultats au pipeline (par exemple, quel en-tête a été traité).

Vue d'ensemble des fonctions publiques

Le tableau suivant répertorie toutes les fonctions publiques non obsolètes de "SIM_DI Process Session SI". Si une fonction existe en plusieurs variantes (surcharges), les paramètres qui peuvent ne pas être présents dans chaque variante sont marqués comme facultatifs.

Fonction Ce qu'elle fait Paramètres (optionnels marqués) Retourne
InitSession Initialise la session avec un document entrant et résout le modèle de processus associé. InboundDocument aucun
InitSession Initialise la session avec un document entrant et un modèle de processus explicitement fourni (et efface d'abord les données de session précédentes). InboundDocument, ProcessTemplate aucun
GetInboundDocument Récupère le document entrant stocké dans la session actuelle. var InboundDocument aucun
GetProcessTemplate Récupère le modèle de processus stocké dans la session actuelle. var ProcessTemplate aucun
ClearProcessSession Efface l'intégralité du contexte de session, y compris le document entrant, le modèle de processus, les clés de mappage et les données de processus. - aucun
ClearProcessData Efface uniquement les données de traitement (en-têtes/lignes/observations temporaires, enregistrements de recherche mappés, id d'en-tête traité) et efface les données correspondantes. - aucun
AddProcessedHeaderRecordId Ajoute un identifiant d'enregistrement d'en-tête de document traité à la liste de session. Prend en charge la mise en correspondance de plusieurs documents. Les entrées en double sont ignorées silencieusement. RecordId aucun
GetProcessedHeaderRecordIds Retourne la liste de tous les identifiants d'enregistrement d'en-tête de document traité collectés lors de l'exécution de la mise en correspondance actuelle. - List of [RecordId]
SetDocumentHeaderVariableRef Stocke une copie de la variable d'en-tête temporaire fournie dans la session (utilisée comme état d'en-tête de session de style référence). var TempDocumentHeader (temporaire) aucun
GetDocumentHeaderVariable Copie les données d'en-tête temporaire actuelles de la session dans la variable d'enregistrement temporaire fournie. var TempDocumentHeader (temporaire) aucun
SetDocumentLineVariableRef Stocke une copie de la variable de ligne temporaire fournie dans la session (utilisée comme état de ligne de session de style référence). var TempDocumentLine (temporaire) aucun
GetDocumentLineVariable Copie les données de ligne temporaires actuelles de la session dans la variable d'enregistrement temporaire fournie. var TempDocumentLine (temporaire) aucun
SetRemarkVariableRef Stocke une copie de la variable d'observation temporaire fournie dans la session (utilisée comme état d'observation de session de style référence). var DocumentRemark (temporaire) aucun
RemoveDocumentRemark Supprime une observation de la session en fonction du numéro de ligne, du numéro de champ et de la zone de validation. LineNo, FieldNo, FieldValidationArea aucun
GetRemarks Copie toutes les observations de session dans la variable d'enregistrement d'observation temporaire fournie. var DocumentRemark (temporaire) aucun
RemoveMappedLookupRecord Supprime la référence d'enregistrement de recherche mappée pour un champ de modèle donné. TemplateFieldRecordId aucun
GetMappedLookupRecord Récupère la référence d'enregistrement de recherche mappée pour un champ de modèle donné (si disponible). TemplateFieldRecordId, var MappedRecordId Boolean
GetDevitatingLinesJsonObject Retourne l'objet JSON décrivant les lignes déviantes et leurs champs non correspondants (y compris le nom de configuration de correspondance et les détails de non correspondance). - JsonObject
GetMatchedLineData Copie toutes les entrées de données de lignes correspondantes dans l'enregistrement temporaire fourni. Chaque entrée contient : numéro de ligne du document, nom de configuration de correspondance, numéro de codeunit d'exécution et les RecordIds de l'en-tête/ligne correspondants. var TempMatchedLineData (temporaire) aucun

3. Personnalisations dans le Processus de Correspondance

3. Personnalisations dans le Processus de Correspondance

Cela n'est nécessaire que si le codeunit est utilisé comme processus personnalisé (si l'option « Ignorer la création lors de la correspondance » dans la configuration de correspondance est active).

Le processus de correspondance dans Smart Processing a été repensé pour prendre en charge l'exécution simultanée de plusieurs configurations de correspondance. Chaque ligne de document entrant peut désormais correspondre de manière indépendante à un en-tête de document cible et une ligne différents (correspondance multi-en-têtes et multi-lignes). Cela signifie que la ligne 1 d'un document entrant peut être affectée à un bon de commande tandis que la ligne 2 va à un autre complètement différent.

Un scénario d'extension courant consiste à déclencher des codeunits personnalisés lors de cette étape de correspondance, en particulier lorsque l'utilisateur termine le brouillon et qu'un ou plusieurs en-têtes cibles ont été créés ou identifiés.

Le codeunit personnalisé continue d'être exécuté avec l'enregistrement d'en-tête du type de document correspondant comme Rec (par exemple, un en-tête d'achat). Cet enregistrement est votre ancre stable pour cet en-tête particulier. Pour déterminer quelles lignes entrantes ont correspondu à cet en-tête (et quel codeunit d'exécution a lancé l'exécution), utilisez GetMatchedLineData de la session et filtrez par "Execution Codeunit No.".

Une fois que votre codeunit a terminé le traitement d'un en-tête, enregistrez-le via AddProcessedHeaderRecordId afin que la plateforme puisse archiver et finaliser correctement tous les en-têtes traités. Étant donné que plusieurs en-têtes peuvent être traités dans la même exécution, chaque codeunit d'exécution est responsable d'ajouter son propre en-tête traité à la liste de session.


4. Exemples de Codeunits de Processus Personnalisés

Exemple 1 : Importer les lignes de réception correspondantes après la création de l'en-tête

codeunit 5673320 "SIM_DI Purch.-Get Receipts"
{
    Description = 'Ce codeunit est utilisé comme codeunit d\'importation de correspondance pour recevoir des lignes de réception d\'achat et créer des lignes de facture d\'achat basées sur les lignes de document correspondantes.';
    TableNo = "Purchase Header"; // <-- Ceci est l'en-tête créé après avoir terminé le traitement
    Access = Internal;

    var
        GlobalCodeunitSIMDIProcessSessionSI: Codeunit "SIM_DI Process Session SI";
        GlobalCodeunitGetReceipts: Codeunit "Purch.-Get Receipt";
        GlobalNoPurchaseReceiptLinesFoundLbl: Label 'Il n\'y a pas de lignes de réception d\'achat pouvant être traitées. Veuillez vérifier les lignes de réception d\'achat et vérifier si elles ont déjà été facturées.';

    trigger OnRun()
    var
        TempLocalRecordSIMDIMatchedLineData: Record "SIM_DI Matched Line Data" temporary;
        LocalRecordPurchRcptLine: Record "Purch. Rcpt. Line";
    begin
        GlobalCodeunitSIMDIProcessSessionSI.GetMatchedLineData(TempLocalRecordSIMDIMatchedLineData);
        TempLocalRecordSIMDIMatchedLineData.SetRange("Execution Codeunit No.", Codeunit::"SIM_DI Purch.-Get Receipts");

        // Filtrer les lignes de réception d'achat en fonction des lignes correspondantes entrantes
        if TempLocalRecordSIMDIMatchedLineData.FindSet() then
            repeat
                if LocalRecordPurchRcptLine.Get(TempLocalRecordSIMDIMatchedLineData."Matched Line RecordId") then
                    LocalRecordPurchRcptLine.Mark(true);
            until TempLocalRecordSIMDIMatchedLineData.Next() = 0;

        // Obtenir les lignes de réception d'achat qui n'ont pas été facturées
        LocalRecordPurchRcptLine.MarkedOnly(true);
        LocalRecordPurchRcptLine.SetFilter("Qty. Rcd. Not Invoiced", '<>0');

        // Si aucune ligne de réception d'achat n'est trouvée, lancer une erreur
        if LocalRecordPurchRcptLine.IsEmpty() then
            Error(GlobalNoPurchaseReceiptLinesFoundLbl);

        // Exécuter le processus pour obtenir les lignes de réception
        GlobalCodeunitGetReceipts.SetPurchHeader(Rec);
        GlobalCodeunitGetReceipts.CreateInvLines(LocalRecordPurchRcptLine);
        Commit();
    end;
}

Cet exemple montre un processus typique piloté par la correspondance : la session fournit la liste des “enregistrements à exécuter”, et votre codeunit effectue un import basé exactement sur ces références correspondantes.


Exemple 2 : Mettre à jour les champs déviants lorsque la déviation est acceptée

codeunit 5673350 "SIM_DI Update Deviating Fields"
{
    Description = 'Ce codeunit peut être utilisé pour mettre à jour les lignes de document liées avec les valeurs entrantes pour les champs qui ont été déviants pendant le processus de correspondance et où la déviation est acceptée.';
    Access = Public;

    var
        GlobalCodeunitSIMDIProcessSessionSI: Codeunit "SIM_DI Process Session SI";
        GlobalCodeunitSIMCOREEvaluate: Codeunit "SIM_CORE Evaluate";
        GlobalCodeunitSIMCOREJSON: Codeunit "SIM_CORE JSON";

    trigger OnRun()
    var
        TempLocalRecordSIMDITempDocumentLine: Record "SIM_DI Temp. Document Line" temporary;
        TempLocalRecordSIMDIMatchedLineData: Record "SIM_DI Matched Line Data" temporary;
        LocalRecordSIMDIInboundDocument: Record "SIM_DI Inbound Document";
        LocalCodeunitSIMDILogManagement: Codeunit "SIM_DI Log Management";
        LocalRecordId: RecordId;
        LocalRecordRef: RecordRef;
        LocalFieldRef: FieldRef;
        LocalRecordIdText: Text;
        LocalDevitatingLinesJsonObject: JsonObject;
        LocalLineMatchingValidationFieldsJsonObject: JsonObject;
        LocalJsonToken: JsonToken;
        LocalFieldNoInteger: Integer;
        LocalFieldNoText: Text;
        LocalCellIndexInteger: Integer;
        LocalMatchingSetupNameText: Text;
        LocalOldValueText: Text;
        LocalNewValueText: Text;
        LocalValueVariant: Variant;
        LocalUpdatedBoolean: Boolean;
        LocalDevitatingFieldUpdateLbl: Label 'La ligne %1 a été mise à jour.\Le champ "%2" a été changé de la valeur "%3" à la valeur "%4".', Comment = '%1 = Numéro de ligne, %2 = Nom du champ, %3 = Ancienne valeur, %4 = Nouvelle valeur';
        LocalNoLinesUpdatedLbl: Label 'Aucune ligne n''a été mise à jour car aucune déviation n''a été acceptée ou les valeurs entrantes sont identiques aux valeurs existantes.';
    begin
        /// Recevoir les données de la session de processus
        LocalDevitatingLinesJsonObject := GlobalCodeunitSIMDIProcessSessionSI.GetDevitatingLinesJsonObject();
        GlobalCodeunitSIMDIProcessSessionSI.GetDocumentLineVariable(TempLocalRecordSIMDITempDocumentLine);
        GlobalCodeunitSIMDIProcessSessionSI.GetMatchedLineData(TempLocalRecordSIMDIMatchedLineData);
        GlobalCodeunitSIMDIProcessSessionSI.GetInboundDocument(LocalRecordSIMDIInboundDocument);

        // Itérer sur les lignes déviantes
        foreach LocalRecordIdText in LocalDevitatingLinesJsonObject.keys() do begin
            if not Evaluate(LocalRecordId, LocalRecordIdText) then continue;
            if not LocalRecordRef.Get(LocalRecordId) then continue;

            // Obtenir l'enregistrement de ligne actuel
            LocalDevitatingLinesJsonObject.Get(LocalRecordIdText, LocalJsonToken);
            // Obtenir le nom de configuration de correspondance
            LocalMatchingSetupNameText := GlobalCodeunitSIMCOREJSON.GetJsonPathValueText(LocalJsonToken.AsObject(), 'MatchingSetupName');

            // Obtenir les champs non correspondants sous forme d'objet json
            if not LocalJsonToken.AsObject().Get('MissMatchedFields', LocalJsonToken) then continue;
            LocalLineMatchingValidationFieldsJsonObject := LocalJsonToken.AsObject();
            // Itérer sur les champs non correspondants et mettre à jour la ligne de document liée avec la valeur entrante si la déviation est acceptée
            foreach LocalFieldNoText in LocalLineMatchingValidationFieldsJsonObject.keys() do begin
                if not Evaluate(LocalFieldNoInteger, LocalFieldNoText) then continue;
                LocalLineMatchingValidationFieldsJsonObject.Get(LocalFieldNoText, LocalJsonToken);

                if not GlobalCodeunitSIMCOREJSON.GetJsonPathValueBoolean(LocalJsonToken.AsObject(), 'AcceptDeviation') then
                    continue;
                if not Evaluate(LocalCellIndexInteger, GlobalCodeunitSIMCOREJSON.GetJsonPathValueText(LocalJsonToken.AsObject(), 'CellIndex')) then
                    continue;

                LocalFieldRef := LocalRecordRef.Field(LocalFieldNoInteger);
                LocalNewValueText := GlobalCodeunitSIMCOREJSON.GetJsonPathValueText(LocalJsonToken.AsObject(), 'IncomingValue');
                LocalOldValueText := LocalFieldRef.Value;

                // Mettre à jour le champ avec la valeur entrante
                GlobalCodeunitSIMCOREEvaluate.EvaluateVariable(
                   LocalNewValueText,
                   Format(LocalFieldRef.Type),
                   LocalValueVariant,
                   false
               );
                LocalFieldRef.Validate(LocalValueVariant);
                LocalRecordRef.Modify();

                // Ajouter une entrée de journal
                LocalCodeunitSIMDILogManagement.AddInformationInTheLog(
                    StrSubstNo(LocalDevitatingFieldUpdateLbl, Format(LocalRecordIdText), LocalFieldRef.Caption, LocalOldValueText, LocalNewValueText),
                    'SIM_DI Update Deviating Fields',
                    LocalRecordSIMDIInboundDocument."Entry No."
                );

                TempLocalRecordSIMDIMatchedLineData.SetRange("Matched Line RecordId", LocalRecordId);
                if TempLocalRecordSIMDIMatchedLineData.FindFirst() then
                    GlobalCodeunitSIMDIProcessSessionSI.AddProcessedHeaderRecordId(TempLocalRecordSIMDIMatchedLineData."Matched Header RecordId");

                LocalUpdatedBoolean := true;
            end;
        end;

        if not LocalUpdatedBoolean then
            Error(LocalNoLinesUpdatedLbl);

        Commit();
    end;
}

Cet exemple illustre le “traitement des déviations piloté par la session” : la session fournit un ensemble de données de déviation structurées ; votre code applique les déviations acceptées, enregistre ce qui a changé et rapporte l'en-tête traité dans la session.


5. Codeunits de Validation de Recherche Personnalisés

En plus des codeunits de processus, Smart Processing prend également en charge les codeunits de validation de recherche personnalisés. Ceux-ci sont exécutés lorsqu'un champ du document entrant est validé et peuvent être utilisés pour rechercher des valeurs dans une table externe et les réaffecter au document.

L'exemple suivant montre comment implémenter un codeunit de validation de recherche personnalisé pour le codeunit standard SIM_DI LookUp No. :

/// <summary>
/// Ce codeunit est utilisé pour rechercher le numéro de l'article, du compte général,
/// de la ressource, de l'immobilisation et du compte de ventilation.
/// </summary>
codeunit 5673301 "SIM_DI LookUp No."
{
    Access = Internal;
    Description = 'Ce codeunit est utilisé pour rechercher le numéro de l\'article, du compte général, de la ressource, de l\'immobilisation et du compte de ventilation.';
    TableNo = "SIM_DI Temp. Document Line";

    trigger OnRun()
    var
        GlobalTypeNotFilledErrLbl: Label 'Pour valider ce champ, le champ "Type" doit être renseigné.';
    begin
        if Rec.Type = Rec.Type::" " then
            Error(GlobalTypeNotFilledErrLbl);

        case Rec.Type of
            Rec.Type::Item:
                LookUpItemNo(Rec);
            Rec.Type::"G/L Account":
                LookUpGLAccountNo(Rec);
            Rec.Type::Resource:
                LookUpResourceNo(Rec);
            Rec.Type::"Fixed Asset":
                LookUpFixedAssetNo(Rec);
            Rec.Type::"Charge (Item)":
                LookUpItemChargeNo(Rec);
        end;
    end;

    local procedure LookUpItemNo(var Rec: Record "SIM_DI Temp. Document Line")
    var
        LocalRecordItem: Record Item;
    begin
        LocalRecordItem.SetRange("No.", Rec."No.");
        if not LocalRecordItem.FindFirst() then begin
            LocalRecordItem.Reset();
            LocalRecordItem.SetRange(Description, Rec."No.");
            if LocalRecordItem.FindFirst() then
                Rec."No." := LocalRecordItem."No.";
        end;
    end;

    local procedure LookUpGLAccountNo(var Rec: Record "SIM_DI Temp. Document Line")
    var
        LocalRecordGLAccount: Record "G/L Account";
    begin
        LocalRecordGLAccount.SetRange("No.", Rec."No.");
        if not LocalRecordGLAccount.FindFirst() then begin
            LocalRecordGLAccount.Reset();
            LocalRecordGLAccount.SetRange(Name, Rec."No.");
            if LocalRecordGLAccount.FindFirst() then
                Rec."No." := LocalRecordGLAccount."No.";
        end;
    end;

    local procedure LookUpResourceNo(var Rec: Record "SIM_DI Temp. Document Line")
    var
        LocalRecordResource: Record Resource;
    begin
        LocalRecordResource.SetRange("No.", Rec."No.");
        if not LocalRecordResource.FindFirst() then begin
            LocalRecordResource.Reset();
            LocalRecordResource.SetRange(Name, Rec."No.");
            if LocalRecordResource.FindFirst() then
                Rec."No." := LocalRecordResource."No.";
        end;
    end;

    local procedure LookUpFixedAssetNo(var Rec: Record "SIM_DI Temp. Document Line")
    var
        LocalRecordFixedAsset: Record "Fixed Asset";
    begin
        LocalRecordFixedAsset.SetRange("No.", Rec."No.");
        if not LocalRecordFixedAsset.FindFirst() then begin
            LocalRecordFixedAsset.Reset();
            LocalRecordFixedAsset.SetRange(Description, Rec."No.");
            if LocalRecordFixedAsset.FindFirst() then
                Rec."No." := LocalRecordFixedAsset."No.";
        end;
    end;

    local procedure LookUpItemChargeNo(var Rec: Record "SIM_DI Temp. Document Line")
    var
        LocalRecordItemCharge: Record "Item Charge";
    begin
        LocalRecordItemCharge.SetRange("No.", Rec."No.");
        if not LocalRecordItemCharge.FindFirst() then begin
            LocalRecordItemCharge.Reset();
            LocalRecordItemCharge.SetRange(Description, Rec."No.");
            if LocalRecordItemCharge.FindFirst() then
                Rec."No." := LocalRecordItemCharge."No.";
        end;
    end;
}

Points de Personnalisation Supplémentaires

Il existe d'autres endroits où des codeunits personnalisés peuvent être intégrés, mais le principe directeur reste constant : utilisez la session pour récupérer le contexte de traitement actif et pour écrire les résultats pertinents au flux de travail. Cela maintient vos extensions cohérentes avec le flux de Smart Processing et empêche l'écart entre l'interface utilisateur et le processus.