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.