Was sind Extensions?

Extensions sind vorwiegend kleine Anwendungen, die die Hauptanwendung Microsoft Dynamics 365 Microsoft Dynamics 365 Business Central um zusätzliche Funktionen erweitern.

Extensions können bequem über die AppSource heruntergeladen und über die Seite/Page Erweiterungsverwaltung innerhalb von Microsoft Dynamics 365 Business Central installiert werden.

Anforderungen

Für die Entwicklung von Extensions sind folgende Punkte zu beachten:

  • Visual Studio Code wird mitsamt der „AL-Extension für Visual Studio Code“ benötigt. Letztere lässt sich schnell und kostenlos in Visual Studio Code herunterladen und installieren.
  • Die AL-Entwicklungsumgebung wird ebenso benötigt. Diese wird bei der Installation von Microsoft Dynamics 365 Business Central mitgeliefert, wenn die Option „moderne Entwicklungsumgebung“ bei der Installation gewählt wurde.
  • Ebenso wird ein Zugriff auf den Server, auf dem Microsoft Dynamics 365 Business Central läuft, benötigt, um die Symbole der verwendeten Objekte zu generieren und die Extension zum Testen in Microsoft Dynamics 365 Business Central zu integrieren.
  • Empfehlenswert wäre es auch, den Debugger aus Visual Studio Code für den Server zu aktivieren, hierfür muss in der Config-Datei des NAV-Servers die Eigenschaft NetFx40_LegacySecurityPolicy auf false gesetzt werden.

Unterschiede zwischen V1- und V2-Extensions

Mit Dynamics NAV 2017 wurde die erste Generation von Erweiterungen eingeführt, die noch in C/SIDE entwickelt werden konnten, bei denen aber einige Punkte zur Veröffentlichung beachtet werden mussten.

Die zweite Generation der Erweiterungen, die Extensions V2, die mit Microsoft Dynamics 365 Business Central mit dem Release von 2018 eingeführt wurden, können zwar nicht mehr in C/SIDE entwickelt werden, dafür ist aber die Veröffentlichung aus Visual Studio Code erheblich einfacher. Die Extensions V2 werden in AL statt wie gewohnt in C/AL programmiert, wobei die Unterschiede sich aber hauptsächlich darauf belaufen, dass Tabellen und Seiten jetzt über sogenannte tableextension- bzw. pageextension-Objekte erweitert werden, statt neue Felder oder Funktionen direkt in die bereits bestehenden Objekte einzufügen.

Außerdem fallen die MenuSuite-Objekte weg, stattdessen stehen vergleichbare Möglichkeiten in Form von neuen Properties bei den Seiten- und Report-Objekten zu Verfügung. Weiter wird die Dotnet-Interoperabilität auf On-Premises-Anwendungen beschränkt, um den Informationsfluss nach außen so gering wie möglich zu halten.

Beispielprojekt

In dieser Schritt-für-Schritt-Anleitung wollen wir am Beispiel einer Ausgabe von Geburtstagsglückwünschen beim Öffnen des Clients zeigen, welche Schritte zur Entwicklung einer kleinen Extension nötig sind.

Allgemeines/Vorbereitung

Bevor die Entwicklung losgehen kann, benötigt Visual Studio Code eine Liste von Symbolen, die die vorhandenen Objekte der zu erweiternden Datenbank darstellen. Diese müssen Serverseitig entweder über den PowerShell-Befehl Compile-NavApplicationObject oder über das Ausführen der finsql.exe, in der die Flag generatesymbolreference=yes gesetzt wurde, erzeugt werden. Dieser Schritt ist notwendig, damit in der Entwicklung Referenzen zu bestehenden Objekten (z. B. Record-Variablen oder zu erweiternde System-Objekte) erkannt und kompiliert werden können.

Die Entwicklung

In dieser Beispiel-Entwicklung erstellen wir eine Codeunit, die beim Starten des Clients prüft, ob der Mitarbeiter, der sich gerade anmeldet, Geburtstag hat, und Glückwünsche ausrichtet, wenn die Prüfung positiv war, sowie die für die Prüfung benötigten Erweiterungen für die Mitarbeiter-Tabelle sowie -Page.

Um die Entwicklung der neuen Extension starten zu können, muss über Strg+Shift+P die Konsoleneingabe in Visual Studio Code aktiviert und über den Befehl AL: Go! ein neues AL-Projekt erstellt werden.

Hier wird der Anwender zuerst aufgefordert, einen Speicherpfad für den Projektordner anzugeben (standardmäßig wird hier ein neuer Ordner im „Dokumente“-Ordner des aktiven Computers vorgeschlagen), und anschließend eine Sandbox-Umgebung sowie einen Server auszuwählen, auf den veröffentlicht werden soll. Eine Auflistung der verschiedenen Sandbox-Umgebungen mit den entsprechenden Vor- und Nachteilen ist in der Sandbox-Übersicht zu finden. Sobald diese Schritte durchgeführt sind, wird das neue Projekt mit einer Beispiel-Anwendung zur Ausgabe von Hello World so wie jeweils einer app.json sowie einer launch.json geöffnet.

Die app.json-Datei enthält eine ID, die als neue GUID bei Erstellung eines neuen Projekts automatisch vergeben wird, sowie diverse Eigenschaften, die für die Extension definiert werden müssen. Einen Überblick über die verschiedenen Eigenschaften gibt hier Microsoft im Al Hilfsblog. Für die Entwicklung relevant sind hier die Eigentschaften „name“, also der Name der Extension, „platform“ die Angabe des Minimal-Builds von Microsoft Dynamics 365 Business Central, für das der Anwender entwickelt, so wie die „idRange“, die vorgibt, in welchem ID-Bereich entwickelt wird. Standardmäßig ist hier für ein neues Projekt der ID-Bereich mit 50100 - 50149 vorbelegt, der volle Umfang der ID-Bereiche, die zur Entwicklung zur Verfügung stehen, ist hier aufgezählt.

In unserem Beispiel sieht die App.json-Datei wie folgt aus, da auf dem lokalen Server schon Entwicklungen stattgefunden haben:

{
  "id": "56394d73-c8fc-4d3e-9b81-2082ec9d3024",
  "name": "Geburtstagsgrüße",
  "publisher": "Default publisher",
  "brief": "",
  "description": "",
  "version": "1.0.0.0",
  "privacyStatement": "",
  "EULA": "",
  "help": "",
  "url": "",
  "logo": "",
  "capabilities": [],
  "dependencies": [],
  "screenshots": [],
  "platform": "13.0.0.0",
  "application": "13.0.0.0",
  "idRange": {
    "from": 50200,
    "to": 50299
  },
  "runtime": "2.0"
}

Die launch.json-Datei enthält Daten zu dem Server, auf dem die Extension aufgespielt werden soll. Hier sind Daten wie Server-Adresse, -Name, -Instanz und -Port gespeichert, die wichtig für das Veröffentlichen der Extension sind. Der Server-Port ist standardmäßig nicht eingeblendet und hat den Standardwert 7049. Wenn der genutzte Port hiervon abweicht muss der Anwender den passenden Wert manuell eingeben. Die anderen Werte sollten auch alle angepasst werden, so dass sie auf den Zielserver deuten und beispielhaft so aussehen:

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "al",
            "request": "launch",
            "name": "SERVER",
            "server": "http://SERVER",
            "port": 7099,
            "serverInstance": "NAVW11300",
            "authentication": "Windows",
            "startupObjectId": 5201,
            "startupObjectType": "Page",
            "breakOnError": true
        }
    ]
}

Nachdem diese beiden Dateien angepasst sind, ist alles bereit für die Entwicklung. Ziel der Entwicklung ist eine Ausgabe von Geburtstagsglückwünschen, die zum passenden Datum einmalig ausgegeben werden. Die einfachste Möglichkeit, dies umzusetzen, ist eine Codeunit, die das Datum auf den in der Mitarbeiter-Tabelle hinterlegten Geburtstag prüft und dort auch hinterlegt, ob schon gratuliert wurde.

Hierfür benötigen wir in der Mitarbeiter-Tabelle eine Möglichkeit, den Nutzer, der sich gerade einloggt, zu identifizieren sowie ein Boolean-Feld um zu hinterlegen, ob besagtem Nutzer schon gratuliert wurde.

Erweitern wir also zunächst die Tabelle. Hierzu legen wir über das Snippet ttableext

eine neue Tableextension an. Der Code sieht nun so aus:
tableextension Id MyExtension extends MyTargetTable
{
    fields
    {
        //Add changes to table fields here
    }

    var
        myInt: Integer
}
Als ID nutzen wir in unserem Beispiel die 50200 (da auf dem Server schon Entwicklungen stattgefunden haben) und als Namen der Tableextension wählen wir EmployeeExt, da wir ja die Mitarbeiter-Tabelle erweitern wollen. Die Ziel-Tabelle ist dementsprechend auch die Employee-Tabelle.
tableextension 50200 EmployeeExt extends Employee

Im Bereich fields fügen wir nun zwei neue Felder über das Snippet tfield ein. Dem ersten Feld, in dem wir die User-ID speichern wollen um später den Nutzer identifizieren zu können, geben wir die ID 50100, den Namen User ID und den Datentyp Code mit einer Maximallänge von 50.

field(50100; User ID; Code[50])

Da die User ID natürlich nicht beliebig gesetzt werden, sondern mit den tatsächlich genutzten IDs übereinstimmen soll, müssen wir noch eine TableRelation auf die User-Tabelle einbauen und da das Feld durch die Nutzer zu pflegen sein soll, sollte auch eine mehrsprachige Caption eingefügt werden. Der hierzu notwendige Code wird vor dem OnValidate-Trigger eingefügt und sollte in etwa wie folgt aussehen:

TableRelation = User.“User Name“;
ValidateTableRelation = false;
CaptionML = DEU = 'Benutzer-ID', ENU = 'User-ID';

Da die Verknüpfung der User ID zum Mitarbeiter eindeutig sein sollte, muss im OnValidate-Trigger des Feldes geprüft werden, ob die User ID bereits einem Mitarbeiter zugeordnet ist. Ebenfalls sollte ein neuer Key für die Tabelle angelegt werden, der die User ID enthält, damit die Suche beim Start der Anwendung zügig ablaufen kann.

Das zweite Feld soll vom Typ Boolean sein und festhalten, ob dem Nutzer schon gratuliert wurde oder nicht. Dementsprechend legen wir das Feld mit der ID 50101, dem Namen Congratulated und dem Typ Boolean an. Da dieses Feld von der Codeunit selbst gefüllt und geprüft werden soll, für die Nutzer aber nicht interessant ist, benötigen wir hier keine Caption.

Die fertige Tableextension, einschließlich sprechender Fehlermeldung, falls die User ID bereits vergeben war, sieht in unserem Beispiel dann so aus:

tableextension 50200 EmployeeExt extends Employee
{
    fields
    {
        field(50100; User ID; Code[50])
        {
            TableRelation = User."User Name";
            ValidateTableRelation = false;
            CaptionML = Deu = 'Benutzer ID', ENU = 'User ID';

            trigger onValidate()
            begin
                SetCurrentKey("User ID");
                SetRange("User ID". Rec."User ID");
                if FindFirst() then
                    Error(UserIDNotUniqueErr);
            end;
        }

        field(50101; Congratulated; Boolean)
        {

        }
    }
    keys
    {
        key(User ID; "User ID")
        {

        }
    }
    var
        UserIDNotUniqueErr: TextConst Deu = 'Die Benutzer-ID darf nur einmal vergeben werden!'
                                      ENU = 'The User ID must not be used twice!'
}

Um die User ID in der Mitarbeiter-Tabelle pflege zu können, muss das Feld auch noch auf der zugehörigen Seite eingeblendet werden. Hierzu erstellen wir eine PageExtension, wozu wir das Snippet tpageext nutzen können.

Der Code sieht nun so aus:

pageextension Id MyExtension extends MyTargetPage
{
    layout
    {
        // Add changes to page layout here
    }

    actions
    {
        // Add changes to page actions here
    }

    var
        myInt: Integer;
}

Wir begnügen uns in diesem Beispiel damit, die User ID auf der Mitarbeiter-Karte einzublenden, da sie nur pflegbar, aber nicht in der Mitarbeiter-Liste direkt einsehbar sein muss. Der PageExtension geben wir dementsprechend die ID 50200 und den Namen EmployeeCardExt, da wir ja die Mitarbeiterkarte erweitern, sowie die Ziel-Seite Employee Card.

Im Layout fügen wir nun das neue Feld User ID als letztes Feld im Allgemein-Fasttab der Karte hinzu. Hierzu nutzen wir den Befehl addlast(General), legen dann das Feld User ID über das snippet(tpagefield) an und vergeben als Bezeichnung sowie als SourceField User ID. Die Eigenschaft FieldPropertyName wird hier standardmäßig eingeblendet, ist aber nicht für die Extension-Entwicklung vorgesehen und kann dementsprechend entfernt werden. Stattdessen benötigen wir noch die Eigenschaft Importance, die wir auf Additional setzen, damit das Feld nur sichtbar wird, wenn der Nutzer auf „Mehr Felder anzeigen“ klickt.

Die fertige PageExtension sieht dann wie folgt aus, nachdem die nicht genutzten Teile der Vorlage entfernt wurden:

pageextension 50200 EmployeeCardExt extends "Employee Card"
{
    layout
    {
        addlast(General)
        {
            field("User ID"; "User ID")
            {
                ApplicationArea = all;
                Importance = Additional;
            }
        }
    }
}

Da wir jetzt die benötigte Einrichtung fertig entwickelt haben, können wir uns an die Codeunit zur Ausgabe der Geburtstagswünsche begeben. Diese erstellen wir, wie schon die table- und pageextensions, durch ein snippet, in diesem Fall tcodeunit:

Der Code sieht nun so aus:

codeunit Id MyCodeunit
{
    trigger OnRun()
    begin

    end;

    var
        myInt: Integer;
}

Da die Meldung beim Start des Clients ausgegeben werden soll, bietet es sich an die Funktion als Eventsubscriber zum OnAfterCompanyOpen-Event der LogInManagement-Codeunit zu erstellen. Dementsprechend benennen wir die Codeunit. Wir setzen also die ID wieder auf 50200 und den Namen, da wir ja auf ein Event der LogInManagement-Codeunit reagieren, auf LoginManagementEvent.

Als nächstes setzen wir die Eventsubscriber-Eigenschaft der Codeunit, hierfür muss das Event vor dem Trigger deklariert werden. Das passiert über eine Codezeile nach dem Muster

[Eventsubscriber(ObjectType::ObjectType,ObjectID(Integer),EventName(Text),ElementName(Text),SkipOnMissingLicense(Boolean),SkipOnMissingPermissions(Boolean))]

, die wir über das Snippet teventsub generieren können.

Der ObjectType ist hierbei die Art des Objekts, in dem das Event veröffentlicht wird, der EventName der Name des Events, das wir abonnieren wollen, der ElementName deklariert zu übergebende Parameter, SkipOnMissingLicense verlässt die Ausführung, wenn keine gültige Lizens für die Extension vorliegt (wird erst relevant, wenn die Extension an Kunden ausgeliefert werden soll). SkipOnMissingPermissions unterbricht die Ausführung, wenn der Nutzer nicht die nötigen Berechtigungen in Microsoft Dynamics 365 Business Central hat.

In unserem Beispiel sieht die entsprechende Deklarationszeile wie folgt aus:

[Eventsubscriber(ObjectType::Codeunit, 40, 'OnAfterCompanyOpen', '', true, true)]

Als nächstes passen wir den Trigger unserer Codeunit an. Statt, wie hier vom Snippet vorgeschlagen, den OnRun-Trigger der Codeunit zu nutzen, erstellen wir eine neue Funktion, der wir einen Namen geben, der widerspiegelt, was wir mit ihr machen wollen. In unserem Beispiel etwa openMessage. Hierfür müssen wir die Zeile

trigger onRun()

in

local procedure openMessage()

abwandeln.

Dieser Funktion geben wir noch eine Record-Variable vom Typ Employee, da alle Prüfungen auf dieser Tabelle ausgeführt werden müssen (User ID, Geburtstag, Bereits gratuliert). Dies passiert in dem einzufügenden var-Abschnitt innerhalb der Funktion, noch vor dem begin..end-Segment, in das wir den Code einfügen.

In diesen Abschnitt tragen wir nun unsere Prüfung ein, hierzu setzen wir den Key der Mitarbeiter-Tabelle auf die User ID und auf dasselbe Feld mit Setrange einen Filter, um zu prüfen, ob der Nutzer, der sich aktuell anmeldet, in der Mitarbeiter-Tabelle vorhanden ist. Wenn ein Mitarbeiter zu der aktuellen User ID gefunden wird, prüfen wir, ob dieser Mitarbeiter am heutigen Datum Geburtstag hat und wenn ja, ob ihm schon gratuliert wurde. Wenn das nicht der Fall ist, werden die Glückwünsche mit der Message-Funktion ausgegeben und das Prüf-Feld Congratulated auf true gesetzt, damit die Gratulation am selben Tag nicht noch einmal ausgeführt wird.

Sollte das aktuelle Datum nicht dem Geburtstag entsprechen, wird geprüft, ob das Feld Congratulated auf true gesetzt ist, und wieder auf false gesetzt, wenn die Prüfung positiv ist, damit dem Mitarbeiter zum nächsten Geburtstag wieder automatisch gratuliert werden kann.

Die fertige Codeunit sieht dann in unserem Beispiel wie folgt aus:

codeunit 50200 LoginManagementEvent
{
    [Eventsubscriber(ObjectType::Codeunit, 40, 'OnAfterCompanyOpen', '', true, true)]
    local procedure openMessage()
    var
        Employee: Record Employee;
    begin
        Employee.SetCurrentKey("User ID");
        Employee.SetRange("User ID", UserId());
        if Employee.FindFirst() then
            if((Date2DMY(Today(), 1) = Date2DMY(Employee."Birth Date", 1))
            AND (Date2DMY(Today(), 2) = Date2DMY(Employee."Birth Date", 2))) then begin
                if Employee.Congratulated = false then begin
                    Message(BirthdayCongratulationsTxt);
                    Employee.Congratulated := true;
                    Employee.Modify();
            end;
        end else
            if Employee.Congratulated = true then begin
                Employee.Congratulated := false;
                Employee.Modify();
            end;
    end;

    var
        BirthdayCongratulationsTxt: TextConst Deu = 'Alles Gute zum Geburtstag!', ENU = 'Happy Birthday!';
}

Installations- und Upgrade-Code

Bei der Entwicklung von Extensions in AL wird noch ein weiteres Hilfsmittel zur Verfügung gestellt, damit den Nutzern der Umgang mit der Extension erleichtern wird: Der Installations- bzw. Upgrade-Code.

Dieser Code wird in eine jeweils separate Codeunit gelegt, bei der der Subtype jeweils auf Install oder Upgrade gesetzt wird.

Die Installations-Codeunit wird immer dann aufgerufen, wenn die Extension neu installiert wird und ist dazu gedacht, Lizenzdaten zu überprüfen oder neue Tabellenfelder mit Inhalt zu füllen.

Da in unserem Fall noch keine Verknüpfung der Mitarbeiter-Tabelle zu einer Tabelle vorliegt, in der die User ID gefüllt ist, müssen wir dieses Hilfsmittel nicht nutzen, um das neue Feld User ID automatisiert zu füllen. Die Pflege dieses Feldes überlassen wir dem Nutzer.

Eine Vorlage für eine Installations-Codeunit sähe wie folgt aus:

codeunit 50201 InstallCodeunit
{
    Subytype = Install;

    trigger OnInstallAppPerCompany()
    begin
        // Hier wird Code ausgeführt, der für die Installation pro Mandant benötigt wird, z.B. das Füllen neuer Felder in Tabellen
    end;

    trigger OnInstallAppPerDatabase()
    begin
        // Hier wird Code ausgeführt, der für die Installation pro Datenbank benötigt wird
    end;
}

Der Upgrade-Codeunit wird ausgeführt, wenn die Extension auf eine neue Version geupgradet wird. Sie wird genutzt, um zu prüfen, ob die Voraussetzungen für das Upgrade erfüllt werden, veränderte oder neue Tabellenfelder zu füllen und nach Abschluss des Upgrades zu prüfen, ob alle Änderungen korrekt ausgeführt wurden.

Dieser Code wird in drei separaten Triggern ausgeführt und kann sowohl pro Mandant, als auch pro Datenbank durchgeführt werden.

Eine Vorlage für eine Upgrade-Codeunit, die Änderungen pro Mandanten prüft und durchführt, sähe wie folgt aus:

codeunit 50202 UpgradeCodeunit
{
    Subtype = Upgrade;

    trigger OnCheckPreconditionsPerCompany()
    begin
        // Hier wird Code ausgeführt, der sicherstellt, dass die Vorraussetzungen für das Upgrade im Mandanten erfüllt sind
    end;

    trigger OnUpgradePerCompany()
    begin
        // Hier wird Code ausgeführt, der für das Upgrade relevante Aufgaben pro Mandant erfüllt, z.B. das Füllen neuer Tabellenfelder
    end;

    trigger OnValidateUpgradePerComapny()
    begin
        // Hier wird Code ausgeführt, der sicherstellt, dass das Upgrade pro Mandant korrekt ausgeführt wurde
    end;
}

Für eine Ausführung pro Datenbank müssten die Trigger von PerCompany auf PerDatabase verändert werden.

Veröffentlichung der Extension

Für die Veröffentlichung der Extension stehen in einer OnPremise-Installation von Microsoft Dynamics 365 Business Central grundsätzlich zwei Wege offen.

Zum einen kann der Anwender, wenn die kompilierte Extension auf dem Server, auf dem Microsoft Dynamics 365 Business Central installiert ist, liegt, diese über PowerShell auf dem Server veröffentlichen. Hierfür wird der Befehl

Publish-NAVApp -ServerInstance YourDynamicsNAVServer -Path “.\MyExtension.app“

verwendet, wobei die Paramter -ServerInstance und -Path den jeweiligen Gegebenheiten angepasst werden müssen.

Nach der Veröffentlichung auf dem Server muss die Extension noch mit dem Server synchronisiert werden. Hierzu wird der Befehl

Sync-NavApp -ServerInstance YourDynamicsNAVServer -Name ExtensionName -Path “.\MyExtension.app” -Tenant TenantID

genutzt, wobei die TenantID hier nur für MultiTenant-Installationen relevant ist, in diesen aber durch die ID desjenigen Mandanten, für den die Extension synchronisiert werden soll, ersetzt werden muss.

Die Installation selbst kann nun entweder über einen weiteren PowerShell-Befehl für einen oder mehrere Mandanten durchgeführt werden, hierzu würden wir den Befehl

Install-NAVApp -ServerInstance YourDynamicsNAVServer -Name ”My Extension” –Tenant Tenant1, Tenant3

benötigen, oder pro Mandanten im Client über die Erweiterungsverwaltung.

Wenn wir die Erweiterungsverwaltung im Microsoft Dynamics 365 Business Central Client (egal ob Web-Client oder, bei der OnPremise-Installation, Windows-Client) öffnen, werden uns nun alle auf dem Server verfügbaren Erweiterungen angezeigt. In der ersten Spalte wird hier angezeigt, ob die jeweilige Erweiterung installiert ist. Durch Doppelklick auf eine Erweiterung erhalten wir weitere Details, z. B. den Herausgeber oder die Beschreibung der Erweiterung, und über die Schaltfläche „Weiter“ können wir die Installation initialisieren. Nach Prüfung der Erweiterungsinformationen kann von hieraus die Installation gestartet werden und die Erweiterung steht, nach Abschluss der Installation und einem Neustart des Clients, zur Verfügung.

Wenn eine Erweiterung nicht mehr benötigt wird, kann sie auf demselben Weg auch wieder deinstalliert werden.

Alternativ kann eine Extension auch über den Debugger in Visual Studio Code veröffentlicht und installiert werden, wenn dieser auf dem Server aktiviert wurde. Hierzu genügt ein einfaches Drücken auf F5 (wobei der Debugger gleich mit gestartet wird, was zum Testen empfehlenswert ist), oder, wenn der Debugger nicht mitgestartet werden soll, Strg + F5. Wenn in der launch.json-Datei eine startupObjectId sowie ein startupObjectType hinterlegt sind, und diese Seite bzw. Tabelle mit den Rechten des Nutzers einsehbar ist, wird der Web-Client von Microsoft Dynamics 365 Business Central direkt mit gestartet und die angegeben Seite bzw. Tabelle aufgerufen. Dadurch kann direkt überprüft werden, ob die neu angelegten Felder auch richtig angezeigt und gefüllt wurden.

Testen der Extension

Wenn die vorherigen Schritte durchgeführt wurden, muss die Extension noch getestet werden.

In unserem Beispiel wäre zu prüfen, ob die Felder in der Einrichtung (Seite und Tabelle) vorhanden sind, ob der OnValidate-Trigger des neuen Feldes User ID richtig prüft, und ob die Codeunit die Geburtstags-Glückwünsche korrekt und nur einmalig pro Geburtstag ausgibt.

Hierzu öffnen wir die Mitarbeiterkarte für einen beliebigen Mitarbeiter, prüfen, ob das Feld „BenutzerID“ vorhanden ist und wenn ja, ob wir es füllen können.

Nachdem wir dies geprüft haben, öffnen wir die Karte für einen anderen Mitarbeiter und prüfen, ob wir hier dieselbe „BenutzerID“ eingeben können.

Nachdem wir auch die Fehlermeldung bei Eingabe einer bereits vergebenen „BenutzerID“ feststellen konnten, können wir in einer OnPremise-Installation die Tabelle aus C/SIDE heraus ausführen und auch hier überprüfen, ob beide Felder angelegt sind.

Zu guter letzt prüfen wir noch, ob die Ausgabe richtig funktioniert, indem wir bei dem Mitarbeiter mit unserer „BenutzerID“ das Geburtsdatum auf den heutigen Tag setzen und den Client neu starten. Beim Neustart sollte nun die Glückwunschmeldung ausgegeben werden:

Wenn wir die Meldung jetzt mit „OK“ wegklicken und anschließend den Client wieder neu starten, sollte die Meldung nicht wieder auftreten.

Um gründlich zu Testen sollte nun noch das Geburtsdatum bei dem Mitarbeiter auf ein anderes gesetzt werden, der Client dann noch einmal neu gestartet, anschließend das Geburtsdatum wieder zurück auf das heutige Datum gesetzt und der Client noch einmal neu gestartet werden.

Wenn hier nur wieder eine Meldung ausgegeben wird, die bei dem Neustart vorher ausblieb, funktioniert unsere Beispiel-Extension wie geplant.