Zum Inhalt

Erweiterung für eigene Tabellen

(Stand 26.11.2025: für Sanscreen OnPrem ab Version 2.1.7, SaaS ab Version 26.1.10.6)

Zielgruppe

Dieser Abschnitt der Online-Dokumentation gehört nicht mehr zum Benutzer-Handbuch für Anwender von Sanscreen.
Er richtet sich an AL-Entwickler, die die Sanscreen Funktionalität für eigene Entitäten nutzen und eine entsprechende Extension entwickeln möchten.
Zum Beispiel für Tabellen in Kunden- oder Branchen-Extensions oder -Apps, die Adressdaten enthalten und über Sanscreen geprüft werden sollen.

Standard Funktionalität

Im Sanscreen Standard sind folgende Tabellen für den gesamten Prüfprozess ausprogrammiert:

  • Customer
  • Vendor
  • Contact
  • "Ship-to Address"
  • "Order Address"
  • Employee
  • "Sales Header"
  • "Purchase Header"
  • Sanscreen AdHoc-Prüfung

Für diese Tabellen (ausgenommen AdHoc) sind folgende Funktionen im Standard enthalten:

  • das Mapping in der Feldzuordnung (auch für AdHoc-Tabelle)
  • die "Ampel"-Controls als Infobox in den Listen- und Stammdaten- bzw. Beleg-Pages
  • über "Ansicht" den Datensatz in der Liste der ausstehenden Prüfungen und den Posten anzeigen lassen
  • das Sperren von Debitor und Kreditor im Falle eines Trefferverdachtes
  • das Zurücksetzen des Sperrkennzeichens an Debitor bzw. Kreditor nach der Trefferbearbeitung
  • die manuelle und automatische Aufbereitung und Prüfung über Aufgabenwarteschlangenposten
  • für Stammdaten-Tabellen die Möglichkeit, bei Neuanlage bzw. Änderung der Adresse den Datensatz in die Liste der ausstehenden Prüfungen (Warteschlange) einzutragen
  • für Belege die Möglichkeit, eine Prüfung bei Freigabe des Beleges durchzuführen

Erweiterungen für eigene Tabellen

Hinweise zur Erweiterbarkeit

Die Funktionalitäten der Punkte 1 bis 6 können für eigene Tabellen über Page-Extensions bzw. die Implementierung von Interfaces erweitert werden.
Die Punkte 7 und 8 können nur über eigene EventSubscriber und für die Steuerung der Funktionen durch Table- und Page-Extensions der Sanscreen Einrichtung oder eine eigene Steuerungs-Tabelle implementiert werden.

Erweiterung der Enum "CESS Entity"

Um die Funktionen für eigene Tabellen zu erweitern, muss zunächst die Enum 5351558 "CESS Entity" mit dem Namen der eigenen Tabelle als "Value Name" erweitert werden.
Hier ein Beispiel für eine Demo-Tabelle "DEMO MyAddress".

enumextension 50000 "DEMO EntityExtMyAddress" extends "CESS Entity"
{
    value(50000; "DEMO MyAddress)
    {
        Caption = 'My Address', Locked = true;
        Implementation = "CESS IMapFields" = "DEMO MapMyAddressFields",
                         "CESS IPrepare" = "DEMO PrepareMyAddress",
                         "CESS IScreenEntity" = "DEMO ShowEntityMyAddress",
                         "CESS IBlockAddress" = "DEMO BlockMyAddress",
                         "CESS IRestoreBlockLvlAddress" = "DEMO RestoreBlockLvlMyAddress";
    }
}

Die Enum "CESS Entity" implementiert folgende Interfaces:

  • "CESS IMapFields"
  • "CESS IPrepare"
  • "CESS IScreenEntity"
  • "CESS IBlockAddress"
  • "CESS IRestoreBlockLvlAddress"

Bei Stammdaten Tabellen könnte die Default-Implementierung für IPrepare verwendet werden.
Für die Erweiterung um Beleg-Tabellen sollte eine eigene Implementierung für das Interface erfolgen.

Mapping in der Feldzuordnung ergänzen

Die Feldzuordnung für die eigene Tabelle kann manuell erfolgen.
Als Komfort für die Anwendenden kann für die Page 5138403 "CESS - Field Mapping" eine Page-Extension erstellt werden, in der eine Action für die Feldzuordnung ergänzt wird.
Hier ein Codebeispiel für die Zuordnung der Adressfelder aus der "DEMO MyAddress" Tabelle:

pageextension 50000 "CESS FieldMapping MyAddress" extends "CESS - Field Mapping"
{
    actions
    {
        addlast("Zuordnung einfügen")
        {
            action(MapMyAddress)
            {
                ApplicationArea = All;
                Caption = 'My Address', Comment = 'Meine Adresse';
                ToolTip = 'Inserts the address fields from the "My Address" table into the "Field Mapping" table.', Comment = 'Trägt die Adress-Felder der Tabelle "Meine Adresse" in die Tabelle "Feldzuordnung" ein.';
                Image = Employee;
                Promoted = true;
                PromotedCategory = Category4;
                PromotedOnly = true;

                trigger OnAction()
                var
                    InsertFieldMapping: Codeunit "CESS Insert Field Mapping";
                begin
                    InsertFieldMapping.InsertFieldMapping(Database::"DEMO MyAddress", false);
                end;
            }
        }
    }
}

Die Codeunit im Beispiel "DEMO MapMyAddressFields" muss das Interface "CESS IMapFields" implementieren.
In der Codeunit sollte dann die vorgefertigte Funktion MapField aufgerufen werden, die das Mapping einträgt.
Hier im Beispiel die Implementierung der Feldzuordnung für die "DEMO MyAddress"-Tabelle:

codeunit 50000 "DEMO MapMyAddressFields" implements "CESS IMapFields"
{
    Access = Internal;

    internal procedure MapFields(HideDialog: Boolean)
    var
        SanscreenQueue: Record "CESS - Queue";
        MyAddress: Record "DEMO MyAddress";
        InsertFieldMapping: Codeunit "CESS Insert Field Mapping";
        FieldMappingInsertedMsg: Label 'Field mapping for "My Address" has been inserted.', Comment = 'Feldzuordnung für "Meine Adresse" wurde eingetragen.';
    begin
        InsertFieldMapping.MapField(Database::"DEMO MyAddress", MyAddress.FieldNo(Name), SanscreenQueue.FieldNo(Name), "CESS Address Type"::" ");
        InsertFieldMapping.MapField(Database::"DEMO MyAddress", MyAddress.FieldNo(Name2), SanscreenQueue.FieldNo("Name 2"), "CESS Address Type"::" ");
        InsertFieldMapping.MapField(Database::"DEMO MyAddress", MyAddress.FieldNo(Address), SanscreenQueue.FieldNo(Address), "CESS Address Type"::" ");
        InsertFieldMapping.MapField(Database::"DEMO MyAddress", MyAddress.FieldNo(Address2), SanscreenQueue.FieldNo("Address 2"), "CESS Address Type"::" ");
        InsertFieldMapping.MapField(Database::"DEMO MyAddress", MyAddress.FieldNo(City), SanscreenQueue.FieldNo(City), "CESS Address Type"::" ");
        InsertFieldMapping.MapField(Database::"DEMO MyAddress", MyAddress.FieldNo(CountryCode), SanscreenQueue.FieldNo("Country Code"), "CESS Address Type"::" ");
        InsertFieldMapping.MapField(Database::"DEMO MyAddress", MyAddress.FieldNo(PostCode), SanscreenQueue.FieldNo("Post Code"), "CESS Address Type"::" ");

        if not HideDialog then
            Message(FieldMappingInsertedMsg);
    end;
}

Page Extension mit Infobox für Liste und Karte

Für Stammdaten kann die Page 5351563 "CESS - FactBox" als Infobox eingebunden werden.
Hier ein Codebeispiel für die "DEMO MyAddress List" Übersicht:

pageextension 50001 DEMO MyAddressListExt extends "DEMO MyAddress List"
{
    layout
    {
        addfirst(factboxes)
        {
            part(CessFactbox; "CESS - FactBox")
            {
                ApplicationArea = All;
                Caption = 'Sanscreen', Comment = 'Sanscreen';
                SubPageLink = "Table No." = const(Database::"DEMO MyAddress"), "Key Text 1" = field("No.");
                Visible = FactboxVisible;
            }
        }
    }

    trigger OnOpenPage()
    begin
        FactboxVisible := true;
        // Hier eigenen Code schreiben, ob die Factbox sichtbar sein soll oder nicht.
        // Im Standard wird geprüft, ob eine Sanscreen-Einrichtung vorhanden ist. Wenn nicht ist die Factbox nicht sichtbar.
        // Wenn die Sanscreen-Einrichtung vorhanden ist, dann wird abgefragt, ob z.B. der "Schalter" für die Debitor-Infoboxen aktiviert ist.
        // Um Schalter für eigene Tabellen zur die Steuerung der Infoboxen zu erhalten, kann entweder die "CESS - Setup" Tabelle erweitert werden,
        // oder es wird Steuerung in einer eigenen Einrichtung implementiert.
    end;

    var
        FactboxVisible: Boolean;
}

Über "Ansicht" den Datensatz in der Liste der ausstehenden Prüfungen und den Posten anzeigen lassen

Um die Anzeige von Datensätzen eigener Tabellen aus der Liste der ausstehenden Prüfung oder den Posten über den Menüpunkt "Ansicht" zu ermöglichen, muss das Interface "CESS IScreenEntity" implementiert werden.
Hier ein Beispiel für die Anzeige von Datensätzen aus "DEMO MyAddress":

codeunit 50001 "DEMO ScreenEntityMyAddress" implements "CESS IScreenEntity"
{
    Access = Internal;

    var
        IntegrationInterface: Codeunit "CESS - Integration Interface";

    internal procedure ShowSourceRecord(SourceType: Integer; SourceId: Text[250])
    var
        MyAddress: Record "DEMO MyAddress";
    begin
        if MyAddress.Get(SourceId) then
            Page.Run(Page::"DEMO MyAddress List", MyAddress);
    end;

    internal procedure ShowHistoryFromAddress(_RecordRef: RecordRef; CompanyName: Text[30]; AddressType: Enum "CESS Address Type")
    begin
        IntegrationInterface.SanscreenManagement_ShowHistoryFromAddress(_RecordRef, CompanyName, AddressType);
    end;
}

Sperren von Stammdaten im Falle eines Trefferverdachtes

Nach der Grundeinrichtung ist in der Sanscreen Einrichtung im Feld "Codeunit nach Treffer" die Sanscreen Standard-Codeunit 5138403 "CESS - Process Hit" eingetragen.
Diese sperrt Debitoren oder/und Kreditoren im Standard-Feld "Gesperrt" und setzt den Gesperrt-Zustand auf "Alle". Dabei werden alle ganz oben erwähnten Tabellen, bis auf Mitarbeiter, berücksichtigt.

Sollen eigene Tabellen eine Sperrung auslösen, ist der empfohlene Weg, eine eigene Codeunit zu erstellen, die das Interface "CESS IBlockAddress" implementiert.

Alternativ könnte in der Sanscreen Einrichtung die Standard-Codeunit 5138403 durch eine mit eigenen Funktionen ergänzte Kopie ersetzt werden.

Hier wieder ein Beispiel mit der "DEMO MyAddress"-Tabelle für das "Sperren" eines MyAddress-Datensatzes, indem er auf Blocked := "Level3" gesetzt wird:

codeunit 50002 "DEMO BlockMyAddress" implements "CESS IBlockAddress"
{
    Access = Internal;

    procedure BlockAddressFromScreenEntry(SanscreenEntry: Record "CESS - Entry")
    var
        [SecurityFiltering(SecurityFilter::Ignored)]
        MyAddress: Record "DEMO MyAddress";
        ProcessHit: Codeunit "CESS - Process Hit";
    begin
        if not MyAddress.Get(SanscreenEntry."Source ID") then
            Error(ProcessHit.GetErrorText(MyAddress.TableCaption(), SanscreenEntry."Source ID"));

        BlockMyAddress(SanscreenEntry, MyAddress);
    end;

    procedure GetBlockedCaption(SanscreenEntry: Record "CESS - Entry"): Text
    begin
        exit(Format("DEMO MyAddress Blocked".FromInteger(SanscreenEntry."Source Blocked Level bef. Hit")));
    end;

    local procedure BlockMyAddress(SanscreenEntry: Record "CESS - Entry"; MyAddress: Record "DEMO MyAddress")
    begin
        SanscreenEntry."Source Blocked Table Id" := Database::"DEMO MyAddress";
        SanscreenEntry."Source Blocked No." := MyAddress."No.";
        SanscreenEntry.Modify();

        SanscreenEntry.StoreBlockedLevel(MyAddress.Blocked);
        MyAddress.Blocked := MyAddress.Blocked::Level3;
        MyAddress.Modify();
    end;
}

Bei einer Implementierung für einen Beleg ist das ein wenig komplexer. Hier ein Beispiel für Service-Belege:

codeunit 50002 "CESS BlockServiceHeaderAddress" implements "CESS IBlockAddress"
{
    Access = Internal;

    procedure BlockAddressFromScreenEntry(SanscreenEntry: Record "CESS - Entry")
    var
        ServiceHeader: Record "Service Header";
        [SecurityFiltering(SecurityFilter::Ignored)]
        Customer: Record Customer;
        ShipToAddress: Record "Ship-to Address";
        IntegrationInterface: Codeunit "CESS - Integration Interface";
        ProcessHit: Codeunit "CESS - Process Hit";
        _RecordRefSalesAddress: RecordRef;
        _RecordRefCustomerAddress: RecordRef;
        _FieldRef: FieldRef;
        SearchIdParts: array[30] of Text[250];
        OptionValue: Integer;
    begin
        IntegrationInterface.SanscreenManagement_SplitSearchID(SanscreenEntry."Source ID", SearchIdParts);
        Evaluate(OptionValue, SearchIdParts[1]);
        if not ServiceHeader.Get(OptionValue, SearchIdParts[2]) then
            Error(ProcessHit.GetDocErrorText(ServiceHeader.TableCaption(), Format(Enum::"Service Document Type".FromInteger(OptionValue)), SearchIdParts[2]));

        //Funktion zum Sperren des Debitors abhängig von Adressart ...
        case SanscreenEntry."Address Type" of
            SanscreenEntry."Address Type"::General:
                begin
                    if ServiceHeader."Customer No." = '' then
                        exit;

                    Customer.Get(ServiceHeader."Customer No.");
                    _RecordRefSalesAddress.GetTable(Customer);
                    _RecordRefCustomerAddress := _RecordRefSalesAddress.Duplicate();
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo(Name));
                    _FieldRef.Value := ServiceHeader.Name;
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo("Name 2"));
                    _FieldRef.Value := ServiceHeader."Name 2";
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo(Address));
                    _FieldRef.Value := ServiceHeader.Address;
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo("Address 2"));
                    _FieldRef.Value := ServiceHeader."Address 2";
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo(City));
                    _FieldRef.Value := ServiceHeader.City;
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo("Country/Region Code"));
                    _FieldRef.Value := ServiceHeader."Country/Region Code";
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo("Post Code"));
                    _FieldRef.Value := ServiceHeader."Post Code";
                    if IntegrationInterface.SanscreenManagement_IsAddressEqual(_RecordRefSalesAddress, _RecordRefCustomerAddress) then
                        SanscreenEntry.BlockCustomer(Customer);
                end;

            SanscreenEntry."Address Type"::Invoice:
                begin
                    if ServiceHeader."Bill-to Customer No." = '' then
                        exit;

                    Customer.Get(ServiceHeader."Bill-to Customer No.");
                    _RecordRefSalesAddress.GetTable(Customer);
                    _RecordRefCustomerAddress := _RecordRefSalesAddress.Duplicate();
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo(Name));
                    _FieldRef.Value := ServiceHeader."Bill-to Name";
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo("Name 2"));
                    _FieldRef.Value := ServiceHeader."Bill-to Name 2";
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo(Address));
                    _FieldRef.Value := ServiceHeader."Bill-to Address";
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo("Address 2"));
                    _FieldRef.Value := ServiceHeader."Bill-to Address 2";
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo(City));
                    _FieldRef.Value := ServiceHeader."Bill-to City";
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo("Country/Region Code"));
                    _FieldRef.Value := ServiceHeader."Bill-to Country/Region Code";
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo("Post Code"));
                    _FieldRef.Value := ServiceHeader."Bill-to Post Code";
                    if IntegrationInterface.SanscreenManagement_IsAddressEqual(_RecordRefSalesAddress, _RecordRefCustomerAddress) then
                        SanscreenEntry.BlockCustomer(Customer);
                end;

            SanscreenEntry."Address Type"::Shipment:
                begin
                    if ServiceHeader."Ship-to Code" = '' then
                        exit;

                    ShipToAddress.Get(ServiceHeader."Customer No.", ServiceHeader."Ship-to Code");
                    _RecordRefSalesAddress.GetTable(ShipToAddress);
                    _RecordRefCustomerAddress := _RecordRefSalesAddress.Duplicate();
                    _FieldRef := _RecordRefSalesAddress.Field(ShipToAddress.FieldNo(Name));
                    _FieldRef.Value := ServiceHeader."Ship-to Name";
                    _FieldRef := _RecordRefSalesAddress.Field(ShipToAddress.FieldNo("Name 2"));
                    _FieldRef.Value := ServiceHeader."Ship-to Name 2";
                    _FieldRef := _RecordRefSalesAddress.Field(ShipToAddress.FieldNo(Address));
                    _FieldRef.Value := ServiceHeader."Ship-to Address";
                    _FieldRef := _RecordRefSalesAddress.Field(ShipToAddress.FieldNo("Address 2"));
                    _FieldRef.Value := ServiceHeader."Ship-to Address 2";
                    _FieldRef := _RecordRefSalesAddress.Field(ShipToAddress.FieldNo(City));
                    _FieldRef.Value := ServiceHeader."Ship-to City";
                    _FieldRef := _RecordRefSalesAddress.Field(ShipToAddress.FieldNo("Country/Region Code"));
                    _FieldRef.Value := ServiceHeader."Ship-to Country/Region Code";
                    _FieldRef := _RecordRefSalesAddress.Field(ShipToAddress.FieldNo("Post Code"));
                    _FieldRef.Value := ServiceHeader."Ship-to Post Code";
                    if IntegrationInterface.SanscreenManagement_IsAddressEqual(_RecordRefSalesAddress, _RecordRefCustomerAddress) then begin
                        Customer.Get(ServiceHeader."Customer No.");
                        SanscreenEntry.BlockCustomer(Customer);
                    end;
                end;
        end;
    end;

    procedure GetBlockedCaption(SanscreenEntry: Record "CESS - Entry"): Text
    begin
        exit(Format("Customer Blocked".FromInteger(SanscreenEntry."Source Blocked Level bef. Hit")));
    end;
}

Für das Sperren von Debitor und Kreditor gibt es die Funktionen "BlockCustomer" bzw. "BlockVendor" in der Tabelle "CESS - Entry". In diesen Funktionen ist alles zum Sperren eine Debitors bzw. Kreditors enthalten.
Das Sperren eines Debitors könnte damit so aussehen:

    procedure BlockAddressFromScreenEntry(SanscreenEntry: Record "CESS - Entry")
    var
        [SecurityFiltering(SecurityFilter::Ignored)]
        Customer: Record Customer;
    begin
        if not Customer.Get(SanscreenEntry."Source ID") then
            Error('%1 %2 not found', Customer.TableCaption(), SanscreenEntry."Source ID");

        SanscreenEntry.BlockCustomer(Customer);
    end;

Austauschen der Standard-"Sperren"-Implementierungen für die ganz oben aufgeführten Tabellen

Sollte für die ganz oben aufgeführten Tabellen eine andere Funktionalität als die Standard-Implementierung gewünscht sein, kann die gefundene Standard-Implementierung für eine Tabelle über EventSubscriber ausgetauscht werden.

Soll zum Beispiel zum Sperren der Debitoren eine eigene Implementierung verwendet werden, dann könnte das so aussehen:

codeunit 50003 "DEMO MyBlockCustomer" implements "CESS IBlockAddress"
{
    procedure BlockAddressFromScreenEntry(SanscreenEntry: Record "CESS - Entry")
    begin
        Message('eigene Customer Block-Implementierung');
    end;

    procedure GetBlockedCaption(SanscreenEntry: Record "CESS - Entry"): Text
    begin
        exit('');
    end;

[EventSubscriber(ObjectType::Codeunit, Codeunit::"CESS - Process Hit" , OnAfterBlockAddressFactory, '', true, true)]
local procedure Handle_OnAfterBlockAddressFactory(SanscreenEntry: Record "CESS - Entry"; var ProviderInstance: Interface "CESS IBlockAddress")
begin
    if SanscreenEntry."Source Type" = Database::Customer then
        ProviderInstance := this;
end;    
}

Entsperren von Stammdaten nach der Trefferbearbeitung

Nach der Grundeinrichtung ist in der Sanscreen Einrichtung im Feld "Codeunit nach Trefferbearbeitung" die Sanscreen Standard-Codeunit 5351555 "CESS - After Handle Hit" eingetragen.
Nach einer Trefferbearbeitung fragt diese Codeunit, ob der Zustand des Debitors bzw. Kreditors auf den Zustand vor dem Treffer zurückgesetzt werden soll.

Um diese Funktionalität zusätzlich für eigene Tabellen zu bekommen, ist der empfohlene Weg, eine eigene Codeunit zu erstellen, die das Interface "CESS IRestoreBlockLvlAddress" implementiert.

Alternativ könnte die Standard-Codeunit 5351555 durch eine mit eigenen Funktionen ergänzte Kopie ersetzt werden.

Hier wieder ein Beispiel mit der "DEMO MyAddress"-Tabelle, für das Zurücksetzen des Status eines "DEMO MyAddress"-Datensatzes auf den Wert vor dem Treffer:

codeunit 50004 "DEMO RestoreBlockLvlMyAddress" implements "CESS IRestoreBlockLvlAddress"
{
    Access = Internal;

    procedure RestoreBlockLvlAddressFromScreenEntry(SanscreenEntry: Record "CESS - Entry")
    var
        [SecurityFiltering(SecurityFilter::Ignored)]
        MyAddress: Record "DEMO MyAddress";
        ProcessHit: Codeunit "CESS - Process Hit";
    begin
        if not MyAddress.Get(SanscreenEntry."Source ID") then
            Error(ProcessHit.GetErrorText(MyAddress.TableCaption(), SanscreenEntry."Source ID"));

        RestoreBlockLvl(SanscreenEntry, MyAddress);
    end;

    local procedure RestoreBlockLvl(SanscreenEntry: Record "CESS - Entry"; MyAddress: Record "DEMO MyAddress")
    var
        AfterHandleHit: Codeunit "CESS - After Handle Hit";
    begin
        if SanscreenEntry."Source Blocked Level bef. Hit" = MyAddress.Blocked.AsInteger() then
            exit;

        if not AfterHandleHit.ConfirmRestoreBlockedLevel(SanscreenEntry, MyAddress.TableCaption(), MyAddress.FieldCaption(Blocked), MyAddress.Blocked,
            "DEMO MyAddress Blocked".FromInteger(SanscreenEntry."Source Blocked Level bef. Hit"))
        then
            exit;

        MyAddress.Blocked := "DEMO MyAddress Blocked".FromInteger(SanscreenEntry."Source Blocked Level bef. Hit");
        MyAddress.Modify();

        SanscreenEntry.MarkRecordAsBlockLevelRestored();
    end;
}

Auch hier ist die Implementierung für einen Beleg ein wenig komplexer. Hier wieder ein Beispiel für Service-Belege:

codeunit 50004 "CESS RestoreBlockLvlService" implements "CESS IRestoreBlockLvlAddress"
{
    Access = Internal;

    procedure RestoreBlockLvlAddressFromScreenEntry(SanscreenEntry: Record "CESS - Entry")
    var
        ServiceHeader: Record "Service Header";
        [SecurityFiltering(SecurityFilter::Ignored)]
        Customer: Record Customer;
        ShipToAddress: Record "Ship-to Address";
        IntegrationInterface: Codeunit "CESS - Integration Interface";
        ProcessHit: Codeunit "CESS - Process Hit";
        _RecordRefSalesAddress: RecordRef;
        _RecordRefCustomerAddress: RecordRef;
        _FieldRef: FieldRef;
        SearchIdParts: array[30] of Text[250];
        OptionValue: Integer;
    begin
        IntegrationInterface.SanscreenManagement_SplitSearchID(SanscreenEntry."Source ID", SearchIdParts);
        Evaluate(OptionValue, SearchIdParts[1]);
        if not ServiceHeader.Get(OptionValue, SearchIdParts[2]) then
            Error(ProcessHit.GetDocErrorText(ServiceHeader.TableCaption(), Format(Enum::"Service Document Type".FromInteger(OptionValue)), SearchIdParts[2]));

        //Funktion zum Sperren des Debitors abhängig von Adressart ...
        case SanscreenEntry."Address Type" of
            SanscreenEntry."Address Type"::General:
                begin
                    if ServiceHeader."Customer No." = '' then
                        exit;

                    Customer.Get(ServiceHeader."Customer No.");
                    _RecordRefSalesAddress.GetTable(Customer);
                    _RecordRefCustomerAddress := _RecordRefSalesAddress.Duplicate();
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo(Name));
                    _FieldRef.Value := ServiceHeader.Name;
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo("Name 2"));
                    _FieldRef.Value := ServiceHeader."Name 2";
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo(Address));
                    _FieldRef.Value := ServiceHeader.Address;
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo("Address 2"));
                    _FieldRef.Value := ServiceHeader."Address 2";
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo(City));
                    _FieldRef.Value := ServiceHeader.City;
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo("Country/Region Code"));
                    _FieldRef.Value := ServiceHeader."Country/Region Code";
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo("Post Code"));
                    _FieldRef.Value := ServiceHeader."Post Code";
                    if IntegrationInterface.SanscreenManagement_IsAddressEqual(_RecordRefSalesAddress, _RecordRefCustomerAddress) then
                        SanscreenEntry.RestoreBlockLvlCustomer(Customer);
                end;

            SanscreenEntry."Address Type"::Invoice:
                begin
                    if ServiceHeader."Bill-to Customer No." = '' then
                        exit;

                    Customer.Get(ServiceHeader."Bill-to Customer No.");
                    _RecordRefSalesAddress.GetTable(Customer);
                    _RecordRefCustomerAddress := _RecordRefSalesAddress.Duplicate();
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo(Name));
                    _FieldRef.Value := ServiceHeader."Bill-to Name";
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo("Name 2"));
                    _FieldRef.Value := ServiceHeader."Bill-to Name 2";
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo(Address));
                    _FieldRef.Value := ServiceHeader."Bill-to Address";
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo("Address 2"));
                    _FieldRef.Value := ServiceHeader."Bill-to Address 2";
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo(City));
                    _FieldRef.Value := ServiceHeader."Bill-to City";
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo("Country/Region Code"));
                    _FieldRef.Value := ServiceHeader."Bill-to Country/Region Code";
                    _FieldRef := _RecordRefSalesAddress.Field(Customer.FieldNo("Post Code"));
                    _FieldRef.Value := ServiceHeader."Bill-to Post Code";
                    if IntegrationInterface.SanscreenManagement_IsAddressEqual(_RecordRefSalesAddress, _RecordRefCustomerAddress) then
                        SanscreenEntry.RestoreBlockLvlCustomer(Customer);
                end;

            SanscreenEntry."Address Type"::Shipment:
                begin
                    if ServiceHeader."Ship-to Code" = '' then
                        exit;

                    ShipToAddress.Get(ServiceHeader."Customer No.", ServiceHeader."Ship-to Code");
                    _RecordRefSalesAddress.GetTable(ShipToAddress);
                    _RecordRefCustomerAddress := _RecordRefSalesAddress.Duplicate();
                    _FieldRef := _RecordRefSalesAddress.Field(ShipToAddress.FieldNo(Name));
                    _FieldRef.Value := ServiceHeader."Ship-to Name";
                    _FieldRef := _RecordRefSalesAddress.Field(ShipToAddress.FieldNo("Name 2"));
                    _FieldRef.Value := ServiceHeader."Ship-to Name 2";
                    _FieldRef := _RecordRefSalesAddress.Field(ShipToAddress.FieldNo(Address));
                    _FieldRef.Value := ServiceHeader."Ship-to Address";
                    _FieldRef := _RecordRefSalesAddress.Field(ShipToAddress.FieldNo("Address 2"));
                    _FieldRef.Value := ServiceHeader."Ship-to Address 2";
                    _FieldRef := _RecordRefSalesAddress.Field(ShipToAddress.FieldNo(City));
                    _FieldRef.Value := ServiceHeader."Ship-to City";
                    _FieldRef := _RecordRefSalesAddress.Field(ShipToAddress.FieldNo("Country/Region Code"));
                    _FieldRef.Value := ServiceHeader."Ship-to Country/Region Code";
                    _FieldRef := _RecordRefSalesAddress.Field(ShipToAddress.FieldNo("Post Code"));
                    _FieldRef.Value := ServiceHeader."Ship-to Post Code";
                    if IntegrationInterface.SanscreenManagement_IsAddressEqual(_RecordRefSalesAddress, _RecordRefCustomerAddress) then begin
                        Customer.Get(ServiceHeader."Customer No.");
                        SanscreenEntry.RestoreBlockLvlCustomer(Customer);
                    end;
                end;
        end;
    end;
}

Auch für das Zurücksetzen von Debitor und Kreditor gibt es Funktionen "RestoreBlockLvlCustomer" bzw. "RestoreBlockLvlVendor" an der Tabelle "CESS - Entry". In diesen Funktionen ist alles zum Zurücksetzen des "Gesperrt"-Status eines Debitors bzw. Kreditors enthalten.

Austauschen der Standard-"Entsperren"-Implementierungen für die ganz oben aufgeführten Tabellen

Sollte für die ganz oben aufgeführten Tabellen eine andere Funktionalität als die Standard-Implementierung gewünscht sein, kann die gefundene Standard-Implementierung für eine Tabelle über EventSubscriber ausgetauscht werden.

Das Zurücksetzen des "Gesperrt"-Status für Debitoren könnte zum Beispiel so aussehen:

codeunit 50005 "DEMO MyRestoreBlockLvlCustomer" implements "CESS IRestoreBlockLvlAddress"
{
    procedure RestoreBlockLvlAddressFromScreenEntry(SanscreenEntry: Record "CESS - Entry")
    begin
        Message('eigene Customer Restore-Implementierung');
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"CESS - After Handle Hit", OnAfterRestoreBlockLvlFactory, '', true, true)]
    local procedure Handle_OnAfterRestoreBlockLvlFactory(SanscreenEntry: Record "CESS - Entry"; var ProviderInstance: Interface "CESS IRestoreBlockLvlAddress")
    begin
        if SanscreenEntry."Source Type" = Database::Customer then
            ProviderInstance := this;
    end;
}

Manuelle und automatische Aufbereitung und Prüfung über Aufgabenwarteschlangenposten

Für die manuelle Aufbereitung erscheinen in einer Liste alle in der Feldzuordnung eingetragenen Tabellen.
In der Sanscreen-Einrichtung werden in einer Liste alle in der Feldzuordnung eingetragenen Tabellen automatisch für die Auswahl zur automatisierten Prüfung eingetragen.
Für Stammdaten-Adressen muss nichts weiter getan werden, damit die Adressen automatisiert aufbereitet werden können.
Es kann das Interface "CESS IPrepare" in einer Codeunit implementiert werden.
Die Funktionen des Interface werden weiter unten erläutert.

Hier ein Beispiel für die "DEMO MyAddress"-Tabelle:

codeunit 50006 "DEMO PrepareMyAddress" implements "CESS IPrepare"
{
    Access = Internal;

    procedure GetCountStepsForTable(PrepareConfig: Record "CESS PrepareConfig"): Integer
    begin
        exit(1);
    end;

    procedure InsertAllDocumentTypes(var PrepareConfig: Record "CESS PrepareConfig")
    begin
        exit;
    end;

    procedure GetDocumentTypeCaption(PrepareConfig: Record "CESS PrepareConfig"): Text
    begin
        exit('');
    end;

    procedure SetRecordRefFilter(var TempPrepareConfig: Record "CESS PrepareConfig" temporary; var TempPrepareConfigArgs: Record "CESS PrepareConfigArgs Table" temporary; var _RecordRef: RecordRef)
    begin
        exit;
    end;
}

In der Liste wird bei Beleg-Tabellen je Dokumentenart eine eigene Zeile angelegt, damit die Belegarten getrennt für die manuelle oder automatisierte Aufbereitung ausgewählt werden können. Außerdem wird in einer Spalte die Belegart angezeigt.

Damit eigene Belege mit mehreren Belegarten in die Liste eintragen werden und die Dokumentenart korrekt angezeigt wird, muss das Interface "CESS IPrepare" in einer eigenen Codeunit implementiert werden.

Hier eine Beispiel-Implementierung des Interface "CESS IPrepare" für Service-Aufträge:

codeunit 50007 "DEMO PrepareServiceHeader" implements "CESS IPrepare"
{
    Access = Internal;

    procedure GetCountStepsForTable(PrepareConfig: Record "CESS PrepareConfig"): Integer
    var
        FieldMapping: Record "CESS - Field Mapping";
    begin
        Steps := 0;
        if FieldMapping.HasFieldMappingForAddressType(PrepareConfig.TableNo, "CESS Address Type"::General) then
            Steps += 1;
        if FieldMapping.HasFieldMappingForAddressType(PrepareConfig.TableNo, "CESS Address Type"::Invoice) then
            Steps += 1;
        if FieldMapping.HasFieldMappingForAddressType(PrepareConfig.TableNo, "CESS Address Type"::Shipment) then
            Steps += 1;
    end;

    procedure InsertAllDocumentTypes(var PrepareConfig: Record "CESS PrepareConfig")
    var
        PrepareConfigHooks: Codeunit "CESS Prepare Config Hooks";
        Ordinals: List of [Integer];
    begin
        Ordinals := Enum::"Service Document Type".Ordinals();
        PrepareConfigHooks.InsertDocumentTypes(Ordinals, PrepareConfig);
    end;

    procedure GetDocumentTypeCaption(PrepareConfig: Record "CESS PrepareConfig") Caption: Text
    begin
        Caption := Format(Enum::"Service Document Type".FromInteger(PrepareConfig.DocumentType));
    end;

    procedure SetRecordRefFilter(var TempPrepareConfig: Record "CESS PrepareConfig" temporary; var TempPrepareConfigArgs: Record "CESS PrepareConfigArgs Table" temporary; var _RecordRef: RecordRef)
    var
        _FieldRef: FieldRef;
    begin
        _FieldRef := _RecordRef.Field(1);  //Document Type
        _FieldRef.SetRange(TempPrepareConfig.DocumentType);
    end;
}

Die Funktion GetCountStepsForTable gibt die Anzahl der für einen Datensatz zu prüfenden Adressen zurück.
Diese Zahl wird für die Anzeige in der manuellen Aufbereitung und für die Protokollierung verwendet.

Die Funktion InsertAllDocumentTypes fügt pro Belegart eine Zeile in die Liste für die automatisierte Aufbereitung ein.
Damit ist für jede Belegart eine automatisierte Aufbereitung konfigurierbar.

Die Funktion GetDocumentTypeCaption zeigt die Caption der jeweiligen Belegart in der Liste an.

Die Funktion SetRecordFilter wird bei der Aufbereitung für jede aufzubereitende Tabelle durchlaufen.
Hier können für diese Tabelle spezifisch Filter gesetzt werden, wie z.B. die Dokumentenart oder bei Kontakten, ob Personenkontakte aufbereitet werden sollen.
Die Argumente sind einmal die aktuelle Zeile der Aufbereitungsliste und eine Argument-Tabelle mit einigen speziellen Feldern, die für die Aufbereitug als Parameter mitgegeben werden können. Zum Beispiel ob Personenkontakte aufbereitet werden sollen.

für Stammdaten: Eintragen bei Neuanlage bzw. Änderung der Adresse in die Warteschlange

Da es nicht möglich ist einen EventSubscriber in einer Interface-Implementierung einzubauen, muss diese Funktion über einen eigenen EventSubscriber für den jeweiligen Stammdatensatz implementiert werden.

Hier wieder am Beispiel für "DEMO MyAddress":

codeunit 50008 "DEMO Modify MyAddress"
{
    Access = Internal;

    [EventSubscriber(ObjectType::Table, Database::"DEMO MyAddress", 'OnAfterModifyEvent', '', false, false)]
    local procedure MyAddress_OnAfterModify(var Rec: Record "DEMO MyAddress"; var xRec: Record "DEMO MyAddress"; RunTrigger: Boolean)
    var
        IntegrationInterface: Codeunit "CESS - Integration Interface";
    begin
        //if <Sollen geänderte "My Address" in die Queue-Tabelle eingetragen werden?> then
        IntegrationInterface.EventHandler_InsertChangedRecordToQueue(Rec, xRec, Rec.IsTemporary, RunTrigger);
    end;

}

Um den Kunden entscheiden zu lassen, ob ein Eintrag gemacht werden soll oder nicht, kann eine Abfrage <Sollen geänderte "My Address" in die Queue-Tabelle eingetragen werden?> (sinngemäß) eingebaut werden.
Das könnte eine Ergänzung in der Einrichtung sein, oder eine eigene Einrichtung in der Kunden-App, oder...

Im Standard wird das für die implementierten Stammdaten-Tabellen über Schalter in der Einrichtung eingestellt.

für Belege: Prüfung der Belegadressen bei Freigabe des Beleges

Da es nicht möglich ist einen EventSubscriber in einer Interface-Implementierung einzubauen, muss diese Funktion über einen eigenen EventSubscriber für die jeweilige Beleg-Tabelle implementiert werden.

Hier ein Beispiel für die Verkaufskopf-Tabelle:

codeunit 50009 "DEMO Release SalesHeader"
{
    Access = Internal;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Release Sales Document", 'OnBeforeReleaseSalesDoc', '', false, false)]
    local procedure Handle_OnBeforeReleaseSalesDoc(var SalesHeader: Record "Sales Header"; PreviewMode: Boolean; var IsHandled: Boolean; var SkipCheckReleaseRestrictions: Boolean; SkipWhseRequestOperations: Boolean)
    var
        IntegrationInterface: Codeunit "CESS - Integration Interface";
    begin
        if IsHandled then
            exit;
        if PreviewMode then
            exit;
        //if not <Soll SalesDocument bei Freigabe geprüft werden?> then
        //    exit;

        if IntegrationInterface.SanscreenDocHasHit(SalesHeader) then
            IsHandled := true;  //keine Freigabe
    end;
}

Auch hier könnte eine Abfrage <Soll SalesDocument bei Freigabe geprüft werden?> erfolgen, mit der der Kunde einstellen kann, ob ein bestimmter Verkaufs-Belegtyp bei Freigabe geprüft werden soll oder nicht.

Im Standard wird das für die implementierten Beleg-Tabellen pro Belegart mit einem Schalter in der Einrichtung eingestellt.

Nachwort

Für Änderungs- oder Ergänzungsvorschläge zum Dokument sind wir dankbar.
Vorschläge gerne an support@comsol.ag.