Zum Inhalt

So entwickeln Sie in Smart Processing (Entwicklerdokumentation)

Status: 01.08.2026 • Lesezeit: ~10 Minuten

Smart Processing bietet Erweiterungspunkte, an denen Sie benutzerdefinierte Logik implementieren können, um Ihre eigenen Prozesse zu starten oder die Handhabung an Kundenanforderungen anzupassen. Dieses Dokument erklärt das zentrale Entwicklungskonzept: Arbeiten Sie durch die aktive Sitzung und lösen Sie benutzerdefinierte Codeunits im entsprechenden Prozess unter Verwendung der Sitzungsdaten aus.

1. Kernkonzept: Arbeiten Sie durch die aktive Verarbeitungssitzung

Smart Processing führt eine „aktive Entwurfssitzung“ aus, während ein Dokument verarbeitet wird. Während dieser Zeit wird der relevante Zustand zentral verwaltet (Dokumentkontext, Vorlagenkontext, übereinstimmende Zeilen, Abweichungen und Zwischendaten, die von der UI und der Verarbeitungspipeline verwendet werden).

Als Entwickler wird der empfohlene Ansatz sein:

  • Lesen Sie den aktuellen Verarbeitungszustand aus der Sitzung.
  • Führen Sie Ihre Logik unter Verwendung des bereitgestellten Kontexts und der Referenzen aus.
  • Schreiben Sie Ergebnisse/Zustände zurück in die Sitzung, wenn der Workflow dies benötigt.
  • Verlassen Sie sich auf die Plattform, um die UI und die Verarbeitung durch die Sitzungsarchitektur synchron zu halten.

Dies macht Anpassungen stabil und vermeidet inkonsistente Zustände zwischen dem, was die Benutzer sehen, und dem, was Ihre Logik geändert hat.


2. Das zentrale Objekt: „SIM_DI Process Session SI“

Die Codeunit „SIM_DI Process Session SI“ ist der zentrale Einstiegspunkt für den Zugriff auf und die Manipulation der derzeit aktiven Smart Processing-Sitzung. Sie fungiert als Hub, um relevante Laufzeitdaten (eingehendes Dokument, Prozessvorlage, Zeilenpuffer, Abweichungsmetadaten) zu erhalten und Ergebnisse zurück an die Pipeline zu kommunizieren (zum Beispiel, welcher Header verarbeitet wurde).

Übersicht der öffentlichen Funktionen

Die folgende Tabelle listet alle öffentlichen, nicht veralteten Funktionen von "SIM_DI Process Session SI" auf. Wenn eine Funktion in mehreren Varianten (Überladungen) existiert, sind Parameter, die möglicherweise nicht in jeder Variante vorhanden sind, als optional gekennzeichnet.

Funktion Was sie tut Parameter (optional gekennzeichnet) Gibt zurück
InitSession Initialisiert die Sitzung mit einem eingehenden Dokument und löst die zugehörige Prozessvorlage auf. InboundDocument keine
InitSession Initialisiert die Sitzung mit einem eingehenden Dokument und einer ausdrücklich bereitgestellten Prozessvorlage (und löscht zuerst die vorherigen Sitzungsdaten). InboundDocument, ProcessTemplate keine
GetInboundDocument Ruft das eingehende Dokument ab, das in der aktuellen Sitzung gespeichert ist. var InboundDocument keine
GetProcessTemplate Ruft die Prozessvorlage ab, die in der aktuellen Sitzung gespeichert ist. var ProcessTemplate keine
ClearProcessSession Löscht den gesamten Sitzungszustand einschließlich eingehendem Dokument, Prozessvorlage, Zuordnungsschlüsseln und Prozessdaten. - keine
ClearProcessData Löscht nur Verarbeitungsdaten (temporäre Header/Zeilen/Anmerkungen, zugeordnete Nachschlageeinträge, verarbeitete Header-ID) und löscht übereinstimmende Daten. - keine
AddProcessedHeaderRecordId Fügt eine verarbeitete Dokument-Header-Datensatz-ID zur Sitzungsliste hinzu. Unterstützt Mehrfachdokument-Übereinstimmung. Doppelte Einträge werden stillschweigend ignoriert. RecordId keine
GetProcessedHeaderRecordIds Gibt die Liste aller verarbeiteten Dokument-Header-Datensatz-IDs zurück, die während des aktuellen Übereinstimmungslaufs gesammelt wurden. - List of [RecordId]
SetDocumentHeaderVariableRef Speichert eine Kopie der bereitgestellten temporären Header-Variable in der Sitzung (wird als Referenzstil-Sitzungsheaderzustand verwendet). var TempDocumentHeader (temporär) keine
GetDocumentHeaderVariable Kopiert die aktuellen temporären Headerdaten der Sitzung in die bereitgestellte temporäre Datensatzvariable. var TempDocumentHeader (temporär) keine
SetDocumentLineVariableRef Speichert eine Kopie der bereitgestellten temporären Zeilenvariable in der Sitzung (wird als Referenzstil-Sitzungszeilenzustand verwendet). var TempDocumentLine (temporär) keine
GetDocumentLineVariable Kopiert die aktuellen temporären Zeilendaten der Sitzung in die bereitgestellte temporäre Datensatzvariable. var TempDocumentLine (temporär) keine
SetRemarkVariableRef Speichert eine Kopie der bereitgestellten temporären Anmerkungsvariable in der Sitzung (wird als Referenzstil-Sitzungsanmerkungszustand verwendet). var DocumentRemark (temporär) keine
RemoveDocumentRemark Entfernt eine Anmerkung aus der Sitzung basierend auf Zeilennummer, Feldnummer und Validierungsbereich. LineNo, FieldNo, FieldValidationArea keine
GetRemarks Kopiert alle Sitzungsanmerkungen in die bereitgestellte temporäre Anmerkungsdatensatzvariable. var DocumentRemark (temporär) keine
RemoveMappedLookupRecord Entfernt die zugeordnete Nachschlageeintragsreferenz für ein gegebenes Vorlagenfeldreferenz. TemplateFieldRecordId keine
GetMappedLookupRecord Ruft die zugeordnete Nachschlageeintragsreferenz für ein gegebenes Vorlagenfeldreferenz ab (sofern verfügbar). TemplateFieldRecordId, var MappedRecordId Boolean
GetDevitatingLinesJsonObject Gibt das JSON-Objekt zurück, das abweichende Zeilen und deren nicht übereinstimmende Felder beschreibt (einschließlich des Namens der Übereinstimmung und der Abweichungsdetails). - JsonObject
GetMatchedLineData Kopiert alle übereinstimmenden Zeilendateneinträge in den bereitgestellten temporären Datensatz. Jeder Eintrag enthält: Dokumentzeilen-Nr., Name der Übereinstimmung, Ausführungs-Codeunit-Nr. und die übereinstimmenden Header/Zeilen-RecordIds. var TempMatchedLineData (temporär) keine

3. Anpassungen im Übereinstimmungsprozess

Dies ist nur erforderlich, wenn die Codeunit als benutzerdefinierter Prozess verwendet wird (wenn die Option „Erstellung bei Übereinstimmung überspringen" in der Übereinstimmungseinrichtung aktiv ist).

Der Übereinstimmungsprozess in Smart Processing wurde neu gestaltet, um gleichzeitige Ausführung mehrerer Übereinstimmungseinrichtungen zu unterstützen. Jede eingehende Dokumentzeile kann nun unabhängig einem anderen Zieldokument-Header und einer anderen Zeile zugeordnet werden (Mehrfach-Header- und Mehrfach-Zeilen-Übereinstimmung). Das bedeutet, dass Zeile 1 eines eingehenden Dokuments einer Bestellung zugewiesen werden kann, während Zeile 2 zu einer völlig anderen geht.

Ein häufiges Erweiterungsszenario besteht darin, benutzerdefinierte Codeunits während dieses Übereinstimmungsschritts auszulösen – insbesondere wenn der Benutzer den Entwurf abschließt und ein oder mehrere Ziel-Header erstellt oder identifiziert wurden.

Die benutzerdefinierte Codeunit wird weiterhin mit dem Header-Datensatz des übereinstimmenden Dokumenttyps als Rec ausgeführt (zum Beispiel ein Einkaufs-Header). Dieser Datensatz ist Ihr stabiler Anker für diesen bestimmten Header. Um zu bestimmen, welche eingehenden Zeilen diesem Header zugeordnet wurden (und welche Ausführungs-Codeunit den Lauf ausgelöst hat), verwenden Sie GetMatchedLineData aus der Sitzung und filtern nach "Execution Codeunit No.".

Sobald Ihre Codeunit die Verarbeitung eines Headers abgeschlossen hat, registrieren Sie ihn über AddProcessedHeaderRecordId, damit die Plattform alle verarbeiteten Header korrekt archivieren und abschließen kann. Da mehrere Header im gleichen Lauf verarbeitet werden können, ist jede Ausführungs-Codeunit dafür verantwortlich, ihren eigenen verarbeiteten Header zur Sitzungsliste hinzuzufügen.


4. Beispiel für benutzerdefinierte Prozess-Codeunits

Beispiel 1: Importieren von übereinstimmenden Empfangszeilen nach der Headererstellung

codeunit 5673320 "SIM_DI Purch.-Get Receipts"
{
    Description = 'Diese Codeunit wird als Matching-Import-Codeunit verwendet, um Einkaufsreceiptszeilen zu empfangen und Einkaufsrechnungszeilen basierend auf den übereinstimmenden Dokumentzeilen zu erstellen.';
    TableNo = "Purchase Header"; // <-- Dies ist der erstellte Header nach Abschluss der Verarbeitung
    Access = Internal;

    var
        GlobalCodeunitSIMDIProcessSessionSI: Codeunit "SIM_DI Process Session SI";
        GlobalCodeunitGetReceipts: Codeunit "Purch.-Get Receipt";
        GlobalNoPurchaseReceiptLinesFoundLbl: Label 'Es gibt keine Einkaufsreceiptszeilen, die verarbeitet werden können. Bitte überprüfen Sie die Einkaufsreceiptszeilen und prüfen Sie, ob sie bereits in Rechnung gestellt wurden.';

    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");

        // Filtern Sie die Einkaufsreceiptszeilen basierend auf den eingehenden übereinstimmenden Zeilen
        if TempLocalRecordSIMDIMatchedLineData.FindSet() then
            repeat
                if LocalRecordPurchRcptLine.Get(TempLocalRecordSIMDIMatchedLineData."Matched Line RecordId") then
                    LocalRecordPurchRcptLine.Mark(true);
            until TempLocalRecordSIMDIMatchedLineData.Next() = 0;

        // Holen Sie sich die Einkaufsreceiptszeilen, die nicht in Rechnung gestellt wurden
        LocalRecordPurchRcptLine.MarkedOnly(true);
        LocalRecordPurchRcptLine.SetFilter("Qty. Rcd. Not Invoiced", '<>0');

        // Wenn keine Einkaufsreceiptszeilen gefunden werden, werfen Sie einen Fehler
        if LocalRecordPurchRcptLine.IsEmpty() then
            Error(GlobalNoPurchaseReceiptLinesFoundLbl);

        // Führen Sie den Prozess aus, um Empfangszeilen zu erhalten
        GlobalCodeunitGetReceipts.SetPurchHeader(Rec);
        GlobalCodeunitGetReceipts.CreateInvLines(LocalRecordPurchRcptLine);
        Commit();
    end;
}

Dieses Beispiel zeigt einen typischen, übereinstimmungsgetriebenen Prozess: Die Sitzung stellt die Liste der „auszuführenden Datensätze“ bereit, und Ihre Codeunit führt einen Import basierend auf genau diesen übereinstimmenden Referenzen durch.


Beispiel 2: Aktualisieren von abweichenden Feldern, wenn die Abweichung akzeptiert wird

codeunit 5673350 "SIM_DI Update Deviating Fields"
{
    Description = 'Diese Codeunit kann verwendet werden, um die verknüpften Dokumentzeilen mit den eingehenden Werten für die Felder zu aktualisieren, die während des Übereinstimmungsprozesses abweichend waren und bei denen die Abweichung akzeptiert wurde.';
    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 'Die Zeile %1 wurde aktualisiert.\Das Feld "%2" wurde von Wert "%3" auf Wert "%4" geändert.', Comment = '%1 = Zeilennr., %2 = Feldname, %3 = Alter Wert, %4 = Neuer Wert';
        LocalNoLinesUpdatedLbl: Label 'Es wurden keine Zeilen aktualisiert, da keine Abweichungen akzeptiert wurden oder die eingehenden Werte mit den vorhandenen Werten identisch sind.';
    begin
        /// Empfangen Sie die Daten aus der Verarbeitungssitzung
        LocalDevitatingLinesJsonObject := GlobalCodeunitSIMDIProcessSessionSI.GetDevitatingLinesJsonObject();
        GlobalCodeunitSIMDIProcessSessionSI.GetDocumentLineVariable(TempLocalRecordSIMDITempDocumentLine);
        GlobalCodeunitSIMDIProcessSessionSI.GetMatchedLineData(TempLocalRecordSIMDIMatchedLineData);
        GlobalCodeunitSIMDIProcessSessionSI.GetInboundDocument(LocalRecordSIMDIInboundDocument);

        // Iterieren Sie über die abweichenden Zeilen
        foreach LocalRecordIdText in LocalDevitatingLinesJsonObject.keys() do begin
            if not Evaluate(LocalRecordId, LocalRecordIdText) then continue;
            if not LocalRecordRef.Get(LocalRecordId) then continue;

            // Holen Sie sich den aktuellen Zeilendatensatz
            LocalDevitatingLinesJsonObject.Get(LocalRecordIdText, LocalJsonToken);
            // Holen Sie sich den Namen der Übereinstimmung
            LocalMatchingSetupNameText := GlobalCodeunitSIMCOREJSON.GetJsonPathValueText(LocalJsonToken.AsObject(), 'MatchingSetupName');

            // Holen Sie sich die nicht übereinstimmenden Felder als JSON-Objekt
            if not LocalJsonToken.AsObject().Get('MissMatchedFields', LocalJsonToken) then continue;
            LocalLineMatchingValidationFieldsJsonObject := LocalJsonToken.AsObject();
            // Iterieren Sie über die nicht übereinstimmenden Felder und aktualisieren Sie die verknüpfte Dokumentzeile mit dem eingehenden Wert, wenn die Abweichung akzeptiert wird
            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;

                // Aktualisieren Sie das Feld mit dem eingehenden Wert
                GlobalCodeunitSIMCOREEvaluate.EvaluateVariable(
                   LocalNewValueText,
                   Format(LocalFieldRef.Type),
                   LocalValueVariant,
                   false
               );
                LocalFieldRef.Validate(LocalValueVariant);
                LocalRecordRef.Modify();

                // Fügen Sie einen Logeintrag hinzu
                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;
}

Dieses Beispiel veranschaulicht die „sitzungsgetriebene Abweichungsbehandlung“: Die Sitzung liefert einen strukturierten Abweichungsdatensatz; Ihr Code wendet akzeptierte Abweichungen an, dokumentiert, was sich geändert hat, und meldet den verarbeiteten Header zurück in die Sitzung.


5. Benutzerdefinierte Nachschlagevalidierungs-Codeunits

Benutzerdefinierte Nachschlage-Codeunits validieren Vorlagenfelder gegen Stammdaten. Konfigurieren Sie dies über Nachschlagevalidierung verwenden = Benutzerdefinierte Codeunit in den Vorlagenfeld-Einstellungen.

Vertrag: TableNo = "SIM_DI Lookup Codeunit"

Eingabefelder: - Parameter – Operationstyp: 'Lookup' (Benutzer klickt auf Nachschlagen) oder 'Validate' (automatische Validierung) - Input Value – Zu validierender Wert oder Suchbegriff - Templ. Field RecordId / Temp. Document Line RecordId – Kontextreferenzen

Ausgabefelder: - Lookup Return Value – Validierter Rückgabewert - Lookup Validation Successtrue wenn gefunden, false wenn fehlgeschlagen - Lookup Error Message – Fehlertext bei fehlgeschlagener Validierung - Lookup Table No. – Verwendete Stammdatentabelle (nur bei Fehlern erforderlich)

Implementierungsmuster

Ihre Codeunit muss:

  1. Beide Betriebsmodi behandeln über case Rec.Parameter
  2. Kontext aus der Sitzung abrufen mit GetProcessTemplate() und GetDocumentLineVariable()
  3. Validierungslogik implementieren: zuerst exakte Übereinstimmung, dann Suchfilter als Fallback
  4. Alle Ausgabefelder setzen und Rec.Modify() aufrufen
  5. Verwandte Felder synchronisieren (z. B. Beschreibung aktualisieren, wenn Nr. validiert wird) mit dem ChangeAdditionalField()-Muster
  6. Klare Fehlermeldungen zurückgeben, wenn die Validierung fehlschlägt

Wichtige Punkte:

  • Für den Modus 'Lookup': Page.RunModal() öffnen und ausgewählten Wert zurückgeben
  • Für den Modus 'Validate': Übereinstimmenden Datensatz suchen, Rückgabewert und Fehlermeldung setzen
  • Immer Lookup Validation Success, Lookup Return Value und Lookup Error Message aktualisieren
  • Separaten RecordRef für Vorab-Validierung verwenden, um die Seitenquelle nicht zu verändern
  • Für typabhängige Validierung: Typ aus der Dokumentzeile abrufen, dynamisch Tabelle/Felder zuordnen

Vollständiges Beispiel

Das folgende Beispiel demonstriert alle Muster einschließlich Typauflösung, bidirektionaler Feldsynchronisierung, zweiphasiger Validierung und Fehlerbehandlung:

codeunit 5673301 "SIM_DI LookUp No."
{
    TableNo = "SIM_DI Lookup Codeunit";
    Description = 'Diese Codeunit wird verwendet, um die Nummer des Artikels, Sachkontos, der Ressource, des Anlageguts und des Zuordnungskontos nachzuschlagen.';
    Access = Public;

    var
        GlobalRecordSIMDIProcessTemplate: Record "SIM_DI Process Template";
        GlobalCodeunitSIMDISession: Codeunit "SIM_DI Process Session SI";
        GlobalTypeNotFilledErrLbl: Label 'Um dieses Feld zu validieren, muss das Feld "Typ" ausgefüllt sein.';

    trigger OnRun()
    var
        LocalRecordSIMDITemplField: Record "SIM_DI Templ. Field";
        LocalRecordRef: RecordRef;
        LocalSalesLineType: Enum "Sales Line Type";
        LocalNoFieldNo: Integer;
        LocalDescFieldNo: Integer;
    begin
        GlobalCodeunitSIMDISession.GetProcessTemplate(GlobalRecordSIMDIProcessTemplate);
        if not LocalRecordSIMDITemplField.Get(Rec."Templ. Field RecordId") then exit;

        // Vertriebszeilentyp aus der Dokumentzeile auflösen
        if not this.ResolveSalesLineType(Rec, LocalSalesLineType) then exit;

        // Tabellen- und Feldkonfiguration für den aufgelösten Typ abrufen
        if not this.GetTypeConfiguration(LocalSalesLineType, LocalRecordRef, LocalNoFieldNo, LocalDescFieldNo) then begin
            Rec."Lookup Validation Success" := false;
            Rec."Lookup Error Message" := GlobalTypeNotFilledErrLbl;
            Rec.Modify();
            exit;
        end;

        case Rec.Parameter of
            'Lookup':
                if this.CreateLookupPage(Rec, LocalRecordRef, LocalNoFieldNo, LocalDescFieldNo) then begin
                    Rec."Lookup Validation Success" := true;
                    Rec."Lookup Return Value" := CopyStr(Format(LocalRecordRef.Field(LocalNoFieldNo).Value), 1, MaxStrLen(Rec."Lookup Return Value"));
                    Rec.Modify();

                    this.ChangeAdditionalField(
                        GlobalRecordSIMDIProcessTemplate."Template Code",
                        Rec."Temp. Document Line RecordId",
                        Format(LocalRecordRef.Field(LocalDescFieldNo).Value)
                    );
                end;
            'Validate':
                this.ValidateNo(Rec, LocalRecordRef, LocalNoFieldNo, LocalDescFieldNo);
        end;
    end;

    /// <summary>
    /// Löst den Vertriebszeilentyp aus dem Dokumentzeilenkontext auf.
    /// </summary>
    local procedure ResolveSalesLineType(var Rec: Record "SIM_DI Lookup Codeunit"; var ParamSalesLineType: Enum "Sales Line Type"): Boolean
    var
        TempLocalRecordSIMDITempDocumentLine: Record "SIM_DI Temp. Document Line" temporary;
        LocalRecordSalesLine: Record "Sales Line";
        LocalTypeText: Text;
        LocalTypeNotExistErrLbl: Label 'Der Typ "%1" existiert nicht. Bitte verwenden Sie einen gültigen Typ.', Comment = '%1 = Type Text';
        LocalInvalidRecordIdErrLbl: Label 'Eine ungültige Datensatz-ID für den Nachschlagevorgabe empfangen.';
    begin
        GlobalCodeunitSIMDISession.GetDocumentLineVariable(TempLocalRecordSIMDITempDocumentLine);
        if not TempLocalRecordSIMDITempDocumentLine.Get(Rec."Temp. Document Line RecordId") then begin
            Rec."Lookup Validation Success" := false;
            Rec."Lookup Error Message" := LocalInvalidRecordIdErrLbl;
            Rec.Modify();
            exit(false);
        end;

        LocalTypeText := TempLocalRecordSIMDITempDocumentLine.GetCellValueViaFieldNo(
            GlobalRecordSIMDIProcessTemplate."Template Code", LocalRecordSalesLine.FieldNo(Type));

        if not Evaluate(ParamSalesLineType, LocalTypeText) then begin
            Rec."Lookup Validation Success" := false;
            Rec."Lookup Error Message" := StrSubstNo(LocalTypeNotExistErrLbl, LocalTypeText);
            Rec.Modify();
            exit(false);
        end;

        exit(true);
    end;

    /// <summary>
    /// Gibt die Tabellenreferenz und Feldnummern für den angegebenen Vertriebszeilentyp zurück.
    /// </summary>
    local procedure GetTypeConfiguration(
        ParamSalesLineType: Enum "Sales Line Type";
        var ParamRecordRef: RecordRef;
        var ParamNoFieldNo: Integer;
        var ParamDescFieldNo: Integer): Boolean
    var
        LocalRecordItem: Record "Item";
        LocalRecordGLAccount: Record "G/L Account";
        LocalRecordResource: Record "Resource";
        LocalRecordFixedAsset: Record "Fixed Asset";
        LocalRecordAllocationAccount: Record "Allocation Account";
    begin
        case ParamSalesLineType of
            ParamSalesLineType::"Charge (Item)",
            ParamSalesLineType::Item:
                begin
                    ParamRecordRef.Open(Database::Item);
                    ParamNoFieldNo := LocalRecordItem.FieldNo("No.");
                    ParamDescFieldNo := LocalRecordItem.FieldNo(Description);
                end;
            ParamSalesLineType::"G/L Account":
                begin
                    ParamRecordRef.Open(Database::"G/L Account");
                    ParamNoFieldNo := LocalRecordGLAccount.FieldNo("No.");
                    ParamDescFieldNo := LocalRecordGLAccount.FieldNo(Name);
                end;
            ParamSalesLineType::Resource:
                begin
                    ParamRecordRef.Open(Database::Resource);
                    ParamNoFieldNo := LocalRecordResource.FieldNo("No.");
                    ParamDescFieldNo := LocalRecordResource.FieldNo(Name);
                end;
            ParamSalesLineType::"Fixed Asset":
                begin
                    ParamRecordRef.Open(Database::"Fixed Asset");
                    ParamNoFieldNo := LocalRecordFixedAsset.FieldNo("No.");
                    ParamDescFieldNo := LocalRecordFixedAsset.FieldNo(Description);
                end;
            ParamSalesLineType::"Allocation Account":
                begin
                    ParamRecordRef.Open(Database::"Allocation Account");
                    ParamNoFieldNo := LocalRecordAllocationAccount.FieldNo("No.");
                    ParamDescFieldNo := LocalRecordAllocationAccount.FieldNo(Name);
                end;
            else
                exit(false);
        end;
        exit(true);
    end;

    /// <summary>
    /// Validiert den Eingabewert durch Abgleich mit dem Feld „Nr.".
    /// </summary>
    local procedure ValidateNo(
        var Rec: Record "SIM_DI Lookup Codeunit";
        var ParamRecordRef: RecordRef;
        ParamNoFieldNo: Integer;
        ParamDescFieldNo: Integer): RecordId
    var
        LocalRemarkMessageLbl: Label 'Die %2 "%1" konnte nicht gefunden werden.', Comment = '%1 = Field Value, %2 = Table';
    begin
        // Exakte Übereinstimmung im Feld „Nr." versuchen
        if StrLen(Rec."Input Value") <= ParamRecordRef.Field(ParamNoFieldNo).Length then begin
            ParamRecordRef.Field(ParamNoFieldNo).SetRange(Rec."Input Value");
            Rec."Lookup Validation Success" := ParamRecordRef.FindFirst();
        end;

        // Suchfilter im Feld „Nr." versuchen
        if not Rec."Lookup Validation Success" then begin
            ParamRecordRef.Reset();
            ParamRecordRef.Field(ParamNoFieldNo).SetRange(Rec."Input Value");
            Rec."Lookup Validation Success" := ParamRecordRef.FindFirst();
        end;

        if Rec."Lookup Validation Success" then begin
            Rec."Lookup Return Value" := CopyStr(Format(ParamRecordRef.Field(ParamNoFieldNo).Value), 1, MaxStrLen(Rec."Lookup Return Value"));
            this.ChangeAdditionalField(
                GlobalRecordSIMDIProcessTemplate."Template Code",
                Rec."Temp. Document Line RecordId",
                Format(ParamRecordRef.Field(ParamDescFieldNo).Value)
            );
        end else
            Rec."Lookup Error Message" := StrSubstNo(LocalRemarkMessageLbl, Rec."Input Value", ParamRecordRef.Caption());

        Rec."Lookup Table No." := ParamRecordRef.Number;
        Rec.Modify();

        if Rec."Lookup Validation Success" then
            exit(ParamRecordRef.RecordId);
    end;

    /// <summary>
    /// Erstellt eine Nachschlageseite zur Anzeige von Datensätzen und ermöglicht dem Benutzer die Auswahl eines Datensatzes.
    /// Vorab-Auswahl eines Datensatzes durch Validierung des Eingabewerts gegen das Feld „Nr.".
    /// </summary>
    local procedure CreateLookupPage(
        var Rec: Record "SIM_DI Lookup Codeunit";
        var ParamRecordRef: RecordRef;
        ParamNoFieldNo: Integer;
        ParamDescFieldNo: Integer): Boolean
    var
        LocalValidationRecordRef: RecordRef;
        LocalRecordId: RecordId;
        LocalVariant: Variant;
    begin
        // Separaten RecordRef für Validierung verwenden, um das Original für die Seite sauber zu halten
        LocalValidationRecordRef.Open(ParamRecordRef.Number);
        LocalRecordId := this.ValidateNo(Rec, LocalValidationRecordRef, ParamNoFieldNo, ParamDescFieldNo);
        if ParamRecordRef.Get(LocalRecordId) then;

        LocalVariant := ParamRecordRef;
        exit(Page.RunModal(0, LocalVariant) = Action::LookupOK);
    end;

    /// <summary>
    /// Ändert den Wert eines zusätzlichen Felds in der Dokumentzeile basierend auf dem Nachschlageergebnis.
    /// Wird verwendet, um das Beschreibungsfeld nach dem Nachschlagen der Nummer zu setzen.
    /// </summary>
    local procedure ChangeAdditionalField(
        ParamTemplateCode: Code[20];
        ParamTempDocumentLineRecordId: RecordId;
        ParamFieldValueText: Text)
    var
        TempLocalRecordSIMDITempDocumentLine: Record "SIM_DI Temp. Document Line" temporary;
        LocalRecordSIMDITemplField: Record "SIM_DI Templ. Field";
    begin
        GlobalCodeunitSIMDISession.GetDocumentLineVariable(TempLocalRecordSIMDITempDocumentLine);
        if not TempLocalRecordSIMDITempDocumentLine.Get(ParamTempDocumentLineRecordId) then exit;
        if not LocalRecordSIMDITemplField.GetViaFieldName(ParamTemplateCode, Enum::"SIM_DI Document Type"::Line, 'Description') then exit;
        if LocalRecordSIMDITemplField."Use Lookup Validation" = LocalRecordSIMDITemplField."Use Lookup Validation"::No then exit;

        TempLocalRecordSIMDITempDocumentLine.SetCellValue(
            TempLocalRecordSIMDITempDocumentLine.GetCellIndex(LocalRecordSIMDITemplField),
            CopyStr(ParamFieldValueText, 1, 250)
        );
        GlobalCodeunitSIMDISession.SetDocumentLines(TempLocalRecordSIMDITempDocumentLine);
    end;
}

Weitere Anpassungspunkte

Es gibt weitere Stellen, an denen benutzerdefinierte Codeunits integriert werden können, aber das leitende Prinzip bleibt konstant: Verwenden Sie die Sitzung, um den aktiven Verarbeitungszustand abzurufen und workflow-relevante Ergebnisse zurückzuschreiben. Dies hält Ihre Erweiterungen konsistent mit dem Smart Processing-Fluss und verhindert UI-/Prozessdrift.