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.