something about Microsoft Dynamics AX RSS 2.0
 Saturday, October 24, 2009

In Microsoft Dynamics AX beziehen Masken ihre Daten, oder anders gesagt die Daten welche sie anzeigen, über so genannte DataSources.
In einer DataSource sind somit die aktuellen auf der Maske angezeigten Daten (lokal) gespeichert.

Zugriff auf den jeweils aktuell ausgewählten Datensatz erhält man üblicherweise über genau diese DataSource.
Der ausgewählte Datensatz kann unter anderem auch, z.B. durch ein MenuItem(Button), an eine Funktion oder andere Maske übergeben werden.
Hierfür muss nur die Eigenschaft “DataSource”, in diesem Beispiel des MenuItem(Button), entsprechend eingestellt sein.

Ist keine DataSource in den Eigenschaften hinterlegt wird immer die erste DataSource der Query verwendet bzw. dessen aktiver Datensatz übergeben.
In der Regel ist dies die DataSource, welche beim Erstellen der Maske als erstes hinzugefügt oder erstellt wurde (Ausnahme ist hier eine eventuelle Manipulation der Query per Programmcode).

Dieses Vorgehen ist für 90% aller Fälle das wohl am besten geeignete Vorgehen und wird in dieser Weise auch vom Dynamics AX Standard verwendet.
Leider gibt es Anwendungsfälle, bei denen diese “starre Verbindung” von DataSource und z.B. Button oder MenuItem nicht funktioniert, beziehungsweise nicht zum gewünschten Ergebnis führt.

Angenommen man hat eine Maske mit zwei DataSources (CustTable und SalesTable), deren Daten über zwei Grids angezeigt werden, sowie einen Button, welcher eine Operation mit dem zuletzt ausgewählten Datensatz (unabhängig von der DataSource) durchführen soll.
Wenn ein Datensatz der DataSource “CustTable” selektiert wurde, soll dieser verarbeitet werden.
Ist zuletzt ein Datensatz der DataSource “SalesTable” selektiert wurden, soll die Operation mit diesem Datensatz erfolgen.

Maske

Bei dieser Anforderung ergibt sich das Problem, dass die Standardvorgehensweise zur Abfrage des selektierten Datensatzes nicht funktioniert, da hierfür eine der DataSources “direkt” angesprochen werden muss.
Welche DataSource nun aber die “aktive” ist, lässt sich leider nicht ermitteln, da standardmäßig jede DataSource einen “aktiven” Datensatz hat und somit eine Unterscheidung, ob der Aufruf für die “CustTable” oder “SalesTable” erfolgen soll, nicht möglich ist.

Über die Methode “docCursor” der Klasse FormRun bietet sich eine zweite Möglichkeit, den aktiven (ausgewählten) Datensatz zu ermitteln.
Dieses Vorgehen wird z.B. vom Dokumentenmangement (Form “DocuView”) verwendet, um den zuletzt gewählten Datensatz zu ermitteln und somit die dem Datensatz zugeordneten Dokumente anzuzeigen.

Leider scheidet dieser Weg ebenfalls aus, da ein “Click” auf den Button zur Folge hat, dass der jeweils aktive Datensatz der “Button-DataSource” durch die Methode “docCursor” zurück geben wird.
Dies ist soweit auch logisch, da ein Button immer einer DataSource zugeordnet ist (entweder über Angabe in der entsprechenden Eigenschaft des Buttons oder, wenn nicht festgelegt, die erste DataSource der Query).

Wie ist es nun aber möglich, dennoch den zuletzt selektierten (ausgewählten) Datensatz zu ermitteln, wenn die im Standard verwendeten Wege nicht funktionieren?

Um das gewünschte Ziel zu erfüllen (bestimmen, welcher der zuletzt selektierte Datensatz ist) muss eine kleine funktionale Erweiterung der “Info” Klasse durchgeführt werden.

Zuerst müssen in der “classDeclaration” der Klasse “Info” zwei neue Variablen/Buffer zum Speichern des selektierten Datensatzes erstellt werden.

final class Info extends xInfo
{
    #SysTaskRecorderMacro
 
    ObjectIdent         docuView;
    ObjectIdent         lastActivatedForm;
 
    ...
 
    // New code -->     
    Common                  lastSelectedRecord;
    Common                  selectedRecord;
    //New code <--
 
    #Define.CurrentVersion(1)
}
 

Nun müssen noch einige neue Methoden für die Klasse “Info” erstellt werden, damit die Variablen/Buffer geschrieben, abgefragt und gelöscht werden können.

private void setLastSelectedRecord(FormRun _formRun)
{
    ;
    if(_formRun.docCursor())
    {
        if(lastSelectedRecord)
        {
            lastSelectedRecord = selectedRecord;
        }
        else
        {
            //Only get the record data, not the cursor
            lastSelectedRecord = _formRun.docCursor().data();
        }
 
        //Only get the record data, not the cursor
        selectedRecord = _formRun.docCursor().data();
    }
}
 
private void clearLastSelectedRecord()
{
    ;
    lastSelectedRecord.clear();
    selectedRecord.clear();
}
 
common lastSelectedRecord()
{
    ;
    return lastSelectedRecord;
}

 

Zum Schluss müssen diese neu erstellten Methoden noch entsprechend in der Methode “formNotify” aufgerufen werden.

void formNotify(FormRun formRun,FormNotify event)
{
    switch (event)
    {
        case FormNotify::Activate:
            this.activate(formRun);
            if (docu)
                docu.reSearch(formRun);
            //New code -->
            this.setLastSelectedRecord(formRun);
            //New code <--
            break;
        case FormNotify::DeActivate:
            break;
        case FormNotify::Open:
            this.open(formRun);
            if (docu)
                docu.set(formRun);
            break;
        case FormNotify::Close:
            this.close(formRun);
            if (docu)
                docu.clear(formRun);
            //New code -->
            this.clearLastSelectedRecord();
            //New code <--
            break;
        case FormNotify::RecordChange:
            if (docu)
                docu.reSearch(formRun);
            //New code -->
            this.setLastSelectedRecord(formRun);
            //New code <--
 
            if (formRun.isWorkflowEnabled())
            {
                // only refresh controls if current ds equals workflow data source
                if (formRun.objectSet().name() == formRun.workflowDataSource().name())
                    formRun.updateWorkflowControls();
            }
 
            break;
        case FormNotify::NoteClicked:
            if (docu)
                docu.note(formRun);
            break;
    }
}
Durch diese kleine Codeänderung kann nun der zuletzt ausgewählte Datensatz, unabhängig von einer DataSource, abgefragt werden.
void clicked()
{
    Common      currentRecord;
    DictTable   dictTable;
    ;
    super();
    //Get the last selected  record
    currentRecord = infolog.lastSelectedRecord();
 
    dictTable = new DictTable(currentRecord.TableId);
 
    setPrefix(tableid2name(currentRecord.TableId));
    info(strfmt("%1 - %2", currentRecord.(dictTable.titleField1()), currentRecord.(dictTable.titleField2())));
}

Bezogen auf die zuvor beschrieben Anforderung könnte das Ergebnis so aussehen.
 

Maske_CustTable  Maske_SalesTable

Saturday, October 24, 2009 5:45:25 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Monday, October 19, 2009

In Microsoft Dynamics AX existiert ein Feature, um Aufgaben (Jobs), welche durch entsprechende Klassen bereit gestellt werden, zu planen und zu einem geplanten Zeitpunkt auszuführen.
Dies wird in Dynamics AX als Stapelverarbeitung (Batch-Framework) bezeichnet.

Jeder Stapelverarbeitungsauftrag verfügt über einen Status, der angibt, in welchem “Zustand” sich der jeweilige Stapelverarbeitungsauftrag befindet.

BatchJobs

Über die Funktion, “Funktionen –> Status ändern” kann dieser Status durch den Benutzer geändert werden.

BatchJobs_statusaendern

Beim Auswählen des “neuen” Status ist leider ein wenig Vorsicht geboten, da bei einem falschen Klick der gesamte Stapelverarbeitungsauftrag unbrauchbar gemacht werden kann.
Drückt man zufällig nicht auf einen der durch die Maske angebotenen Werte, so wird der Status der Stapelverarbeitungsauftrags gelöscht.

BatchJobs_OhneStatus

Das unschöne hierbei ist, dass dieses “Status löschen “nicht mehr rückgängig gemacht werden kann (jedenfalls nicht durch die Dynamics AX Masken).
Bei dem Versuch, wieder einen korrekten Status zu vergeben (ebenfalls über die Funktion “Status ändern”) wird leider nicht der gewünschte Status gesetzt, sondern eine Fehlermeldung ausgegeben.

Fehlermeldung_BatchStatus

Die einzige Möglichkeit, wieder eine korrekten Status zu setzten, besteht leider darin, einen kleinen Job zu schreiben (mit X++), welcher den Status per Programmcode ändert.

Ist gerade kein Entwickler “zur Hand”, besteht nur die Möglichkeit, den Stapelverarbeitungsauftrag zu löschen und erneut anzulegen (dies kann aber von Fall zu Fall sehr aufwändig sein).

Monday, October 19, 2009 7:41:39 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Thursday, October 01, 2009

Wie bereits an mehreren Stellen angekündigt, hat Microsoft das HotFix Rollup 3 für Microsoft Dynamics AX 2009 (RTM und SP1) veröffentlicht.

Alle Microsoft Dynamics AX Kunden und Partner können das Hotfix Rollup über das Customer Source bzw. Partner Source beziehen.

HotFix Rollup 3 für Dynamics AX 2009 RTM (KB974407)

HotFix Rollup 3 für Dynamics AX 2009 SP1 (KB974409)

Thursday, October 01, 2009 7:48:49 AM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Thursday, September 17, 2009

In mehreren Artikeln wurde bereits beschrieben, wie LookupForms erstellt werden müssen, um alle Funktionen bereit zu stellen, die auch durch einen Standard-Lookup bereit gestellt werden.

Ein guter Artikel ist zum Beispiel auf Axaptapedia zu finden:
http://axaptapedia.com/Lookup_Form

Leider wurde in diesem Artikel auf eine Kleinigkeit nicht hingewiesen, die allerdings für sehr viel Verwirrung sorgen kann.

Um beim Öffnen des Lookups den bereits eingetragenen Wert zu selektieren (in dem Control der aufrufenden Maske), müssen wie in dem Artikel beschrieben, die Methoden „executeQuery“ und „init“ der DataSource der Lookup-Maske überschrieben werden.

Beispiel:

public void executeQuery()
{
    FormStringControl   callerControl   = SysTableLookup::getCallerStringControl(element.args());
    ;
    super();
 
    xyz_ds.findValue(fieldnum(xyz,id),callerControl.text());
}

public void init()
{
    Query                q = new Query();
    QueryBuildDataSource qbds;
    ;
    super();
 
    qbds = q.addDataSource(tablenum(xyz));
    qbds.orderMode(OrderMode::OrderBy);
    qbds.addSortField(fieldNum(xyz,some_other_field));
    this.query(q);
}

Es wird auch beschrieben, dass in der Methode “init” der Datasource eigene Ranges oder Sortings definiert werden können.
Dies ist soweit auch richtig, allerdings mit einer Ausnahme.

Wird auf dem Feld, welches bei dem Aufruf von „Datasource.findValue“ in der Methode „init“ angegeben wurde (sollte auch immer das Feld sein, dessen Wert durch den Lookup ausgewählt wird), eine Range definiert, so funktioniert die Selektion des zuvor gewählten Wertes nicht mehr und es wird immer der erste Wert im Lookup selektiert bzw. ausgewählt.

Beispiel:

public void executeQuery()
{
    FormStringControl   callerControl   = SysTableLookup::getCallerStringControl(element.args());
    ;
    super();
 
    xyz_ds.findValue(fieldnum(xyz,id),callerControl.text());
}

public void init()
{
    Query                q = new Query();
    QueryBuildDataSource qbds;
    QueryBuildRange      range;
    ;
    super();
 
    qbds = q.addDataSource(tablenum(xyz));
    qbds.orderMode(OrderMode::OrderBy);
    qbds.addSortField(fieldNum(xyz,some_other_field));
    range = qbds.addRange(fieldnum(xyz,id));
    range.value(SysQuery::valueNot(<someValue>));
    this.query(q);
}

Dieses Verhalten lässt sich allerdings umgehen, wenn anstelle des Aufrufs von “DataSource.findValue” in der „ExecuteQuery“ Methode der DataSource der Aufruf von „DataSoucre.findRecord“ verwendet wird.
Hierfür muss aber der entsprechende Datensatz des zuvor oder bereits ausgewählten Wertes ermittelt werden um diesen beim Aufruf von „DataSource.findRecord“ als Parameter zu übergeben.

Beispiel:

public void executeQuery()
{
    FormStringControl   callerControl;
    Xyz                 xyzRecord;
    ;
    callerControl = SysTableLookup::getCallerStringControl(element.args());
    xyzRecord = Xyz::find(callerControl.text());
 
    super();
 
    xyz_ds.findRecord(xyzRecord);
}

public void init()
{
    Query                q = new Query();
    QueryBuildDataSource qbds;
    QueryBuildRange      range;
    ; 
    super();
 
    qbds = q.addDataSource(tablenum(xyz));
    qbds.orderMode(OrderMode::OrderBy);
    qbds.addSortField(fieldNum(xyz,some_other_field));
    range = qbds.addRange(fieldnum(xyz,id));
    range.value(SysQuery::valueNot(<someValue>));
    this.query(q);
}

Es muss also darauf geachtet werden, ob eine Einschränkung (Range) auf dem „ID-Feld“ benötigt wird oder nicht.

Wird keine Einschränkung benötigt, kann, wie in dem Artikel auf Axaptapedia beschrieben, mit „DataSource.findValue“ gearbeitet werden um den entsprechenden Datensatz zu selektieren.
Wird aber eine solche Einschränkung benötigt, muss mit „DataSoucre.findRecord“ gearbeitet werden.

Thursday, September 17, 2009 9:48:11 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Saturday, September 12, 2009

Wird Microsoft Dynamics AX 2009 mit Windows Server 2008 und SQL Server 2008 installiert, kann es zu einem Problem bei Bereitstellen der ODC-Dateien kommen.

Nach dem Aufruf der Funktion „ODC-Dateien bereitstellen“ meldet Dynamics AX 2009 einen Fehler im Infolog.

ODC_Error

Diese Fehlermeldung wird ebenfalls im Ereignislog von Windows protokolliert.

Laut einem Artikel im EMEA Dynamics AX Support Blog ist hierfür ein Hot Fix erhältlich.
https://blogs.msdn.com/emeadaxsupport/archive/2009/04/23/unable-to-deploy-odc-files-to-enterprise-portal-even-after-installing-hotfix-kb960158.aspx

Wurde allerdings schon das Hot Fix Rollup 2 für Dynamics AX 2009 (SP1) installiert, wird das beschriebene Hot Fix nicht mehr benötigt.

Um nun die ODC-Dateien erfolgreich bereitstellen zu können muss wie folgt vorgegangen werden:

  • Das bereits installierte Enterprise-Portal muss aktualisiert werden
    (Verwaltung/Einstellungen/Internet/Enterprise Portal/Bereitstellungen verwalten/ Button „Aktualisieren“ wählen)
  • Nun (sicherheitshalber) den IIS neu starten, z.B. durch Aufruf von „iisreset“
  • Anschließend können die ODC-Dateien bereit gestellt werden.
    (Verwaltung/Einstellungen/Unternehmensanalyse/OLAP/Olap-Verwaltung/ Button „ODC-Dateien bereitstellen“ wählen)

Ob das Bereitstellen der ODC-Dateien funktioniert hat, kann überprüft werden, indem man kontrolliert, ob die entsprechende Bibliothek im SharePoint die ODC Dateien enthält.
http://<servername>/websites/DynamicsAx/Data%20Connections/Forms/AllItems.aspx

Saturday, September 12, 2009 2:11:03 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Thursday, September 03, 2009

Ab sofort führt mein Arbeitgeber, die Firma AX Solutions GmbH, einmal in der Woche (mittwochs 15:30 - 16:30 Uhr) eine kostenlose „Experten-Sprechstunde“ durch.

Diese „Sprechstunde“ richtet sich an Microsoft Dynamics AX Bestandskunden sowie Interessenten die Antworten auf bisher unbeantwortete Fragen oder einen Rat zu einer speziellen Problemstellung suchen.

Unter anderem werde auch ich diese Sprechstunden abhalten und versuchen nach besten Wissen und Gewissen Rat zu geben. Sicherlich wird sich nicht jede Problemstellung Ad-hoc lösen lassen. Ich bin aber sicher, dass der gemeinsame Dialog zumindest Lösungsoptionen aufzeigen wird.

Guter Rat ist Sprichwörtlich teuer. Diesmal aber nicht, denn die Dynamics AX Sprechstunde ist kostenlos.
Auf die sonst übliche Praxisgebühr in Höhe von 10,- EUR pro Quartal wird verzichtet. :-)

Da die Sprechstunden in einem 1:1 Gespräch durchgeführt werden, und möglichst vielen Ratsuchenden die Möglichkeit gegeben werden soll diese zu nutzen, ist jede Sprechstunde auf 60 Minuten begrenzt.

Ratsuchende bzw. Interessierte möchte ich bitte, sich unter folgendem Link: (http://www.ax-solutions.de/kontaktformular.html) mit dem Stichwort „Dynamics AX – Sprechstunde“ anzumelden.

Weitere Informationen können über die  Webseite der AX Solutions GmbH bezogen werden.

Thursday, September 03, 2009 5:06:30 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Thursday, July 30, 2009

Wie schon die erste Auflage des Buches „Inside Dynamics AX“ ist dieses Buch eine sehr gute Ergänzung zu den von Microsoft angebotenen Schulungsunterlagen (Development 1-4).

Angefangen bei der Architektur, der Entwicklungsumgebung und –Tools, bis hin zu Code Upgrades beschreibt dieses Buch alle Themen die für einen AX Entwickler von Bedeutung sind.

Nicht nur alle neuen Features von Dynamics AX 2009, z.B. Dynamics AX Reporting Services oder Workflows, sondern auch ältere Features wie z.B. das Application Integration Framework (AIF), werden wesentlich detaillierter beschrieben als an anderen Stellen.

Leider gibt es auch Bereiche, die nicht so detailliert besprochen werden bzw. wo einige Fragen nicht gänzlich beantwortet werden.
Ein Beispiel hierfür ist die .NET Integration. Zwar wird der Business Connector ausreichend beschrieben, aber das Thema CLR-Interoperability wird leider nur sehr knapp behandelt.

Einige Kapitel wurden im Vergleich zu der ersten Auflage des Buches gänzlich überarbeitet.
Beispielhaft sei das Kapitel über Form Customizations genannt, welches komplett neu geschrieben wurde.

Leider hat dies auch zur Folge, dass einige sehr gut Beschriebene Themen, wie Beispielweise das dynamische Anpassungen von Masken mit X++,
jetzt nicht mehr behandelt werden.

Was dieses Buch aber nicht beschreibt oder behandelt, sind die Klassen, Tabellen, API‘s, etc. des Microsoft Dynamics AX Standards.
Dies würde allerdings auch den Rahmen des Buches mehr als sprengen.

In der Gesamtbetrachtung ist die neue Auflage von Inside Microsoft Dynamics AX eins der besten technischen Bücher über Microsoft Dynamics AX.
Kein Buch geht soweit in die Tiefe wie dieses. Egal ob Anfänger oder erfahrener Entwickler, für jeden ist etwas dabei.

Auch wer schon die erste Auflage von Inside Microsoft Dynamics AX gelesen hat, wird viele neue Themen finden.

Thursday, July 30, 2009 3:28:50 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


Wird die Eigenschaft(Property) “AllowEditOnCreate” eines Tabellenfeldes auf den Wert “No” gesetzt, ist es nicht möglich,
Werte für dieses Tabellenfeld über das AIF (Application Integration Framework) zu schreiben (Insert-Operation).

Alle Tabellenfelder, welche diese Eigenschaft auf “No” gesetzt haben, werden durch das AIF automatisch auf deren Default-Wert gesetzt und jegliche Wert der AIF Nachricht werden ignoriert.
Dies hat zur Folge, dass wenn das Tabellenfeld kein Enum ist, das Tabellenfeld immer leer ist.

Da dieser Automatismus schon vor Ausführung der AX<Table> Klasse greift, der Wert also schon beim Ausführen der entsprechenden Parm-Methode “leer” ist,
kann dieses Verhalten ohne Änderung der AIF-Basis Klassen nicht geändert werden.

Thursday, July 30, 2009 12:09:08 AM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Wednesday, July 29, 2009

Egal ob Anfänger oder schon erfahrener Dynamics AX Entwickler.
Von Zeit zu Zeit tauchen immer die gleichen Fragen und/oder Problemstellungen auf.

Was immer mal wieder in Foren oder Newsgroups zu lesen ist, ist die Frage, wie die Berechnung des Artikelpreises für eine Debitor mit X++ Code durchgeführt werden kann.
Der Standard von Microsoft Dynamics AX bringt zwar eine Maske zur “Preisabfrage” mit sich, welche diese Problemstellung eigentlich schon löst, allerdings ist es manchmal
notwendig, die Preisberechnung z.B. in einer Klasse durchzuführen, um mit dem Ergebnis weitere Operationen durchführen zu können.

Maske zur Preisberechnung

Da in Dynamics AX Preise für einen Artikel nicht nur “direkt” im Artikelstamm, sondern auch in den Handelsvereinbarungen, hinterlegt werden können, stellt der Standard von
Dynamics AX die Klasse “PriceDisc” zur Verfügung, welche alle Möglichkeiten der Preispflege berücksichtig.
Unter Anderem werden auch Rabatte bzw. Zuschläge der Handelsvereinbarungen durch diese Klasse berücksichtigt.

Die Klasse “PriceDisc” stellt die statische Methode “actualSalesPriceDisc” bereit. Mit dieser können z.B. der Verkaufspreis pro Preiseinheit, der Rabatt oder die Zuschläge
berechnet und/oder ermittelt werden.
Durch eine zweite statische Methode “price2Amount” lässt sich anschließend der Nettobetrag eines Artikels ausrechnen.

Beispiel:

CustTable               custTable = CustTable::find("5010");
InventTable             inventTable = InventTable::find("1000");
Qty                     qty = 1.00;
 
SalesPrice              salesPrice; // Verkaufspreis je Preiseinheit
PriceMarkup             salesMarkup; //Preis sonst. Zuschläge
PriceUnit               priceUnit; // Preiseinheit
SalesLineDisc           salesLineDisc; // Rabatt
SalesLinePercent        salesLinePercent;
DiscPct                 percent1;
DiscPct                 percent2;
PriceDiscTable          actualPrice; // Gefundener Datensatz der Handelsvereinbarung (Verkaufspreis)
PriceDiscTable          actualDisc; // Gefundener Datensatz der Handelsvereinbarung (Rabatte)
AmountCur               lineAmount; // Nettobetrag
;
[salesPrice, salesMarkup, priceUnit,
 salesLineDisc, salesLinePercent, percent1,
 percent2, actualPrice, actualDisc] = PriceDisc::actualSalesPriceDisc(custTable, inventTable, qty);
 
lineAmount = PriceDisc::price2Amount(salesPrice, priceUnit, salesLineDisc, qty,
                                     qty, salesMarkup, salesLinePercent,
                                     custTable.Currency, 0);
Wednesday, July 29, 2009 5:48:40 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Thursday, June 18, 2009

Die neue oder zweite Auflage von "Inside Mircosoft Dynamics AX" ist vor wenigen Tage erschienen.

Inside Mircosoft Dynamics AX 2009

Wie auch schon die erste Auflage des Buches, welche auf der Version 4.0 von Microsoft Dynamics AX basierte, ist diese Buch in erster Linie für Entwickler gedacht.
Die aktuelle Auflage umfasst gut 100 Seiten mehr als die erste Auflage und ist leider auch im Preis etwas teurer.

Weitere Informationen über den Inhalt können z.B. auf den Seiten von Amazon entnommen werden.

Es ist zu hoffen, dass sich diese Auflage auf gleichem Level wie die erste Auflage bewegt und somit zu einem "Must-Have" oder "Must-Read" für AX Entwickler wird.

 

Thursday, June 18, 2009 7:56:05 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Saturday, April 04, 2009

In dem Artikel "Anzeige von Lagerdimensionen auf Masken" wurde bereits gezeigt, was zu tun ist, um die Anzeige von Lagerdimensionen auf Masken dynamisch anpassen zu können bzw. das Standardverhalten für die Anzeige von Lagerdimensionen zu implementieren.

Manchmal soll eine ähnliche Funktionalität auch für Berichte bereit gestellt werden, um zum Beispiel vor der Berichtserstellung auswählen zu können, welche Lagerdimensionen auf dem Bericht(Report) angedruckt werden.
Auch hierfür sind im Dynamics AX Standard die entsprechenden Funktionalitäten (oder besser Klassen) bereits vorhanden, sodass diese nur verwendet werden müssen.

Als Ausgangsbasis für den Bericht dient ebenfalls die Tabelle „AKU_DemoTable“.

Der Bericht soll nun, die in dieser Tabelle gespeicherten Datensätze andrucken/ausgeben.

Wie bei Masken, muss auch für einen Bericht, die Query entsprechend um die Tabelle InventDim ergänzt werden.
Hierbei ist zu beachten, dass die Eigenschaften (Properties) „FetchMode“ auf „1:1“ und „Relations“ auf „Yes“ gesetzt werden.

Als nächstes muss nun, ebenfalls analog zu dem Vorgehen bei Masken, die Feldgruppe „InventoryDimensions“ in den Designzweig des Reports aufgenommen werden.
Beispielhaft wird diese in einem Body-Element erstellt.

Nun müssen noch einige Anpassungen an den Methoden des Berichts durchgeführt werden, damit das gewünschte Ergebnis erreicht werden kann.
Bezogen auf die Möglichkeit, die zu druckenden Lagerdimensionen bestimmen zu können, müssen die Methoden „classDeclaration“, „run“, „dialog“ und „getFromDialog“ wie folgt überschrieben werden.
Auch das Überschreiben der Methoden „pack“ und „unpack“ ist hilfreich (für die Lagerdimensionsanzeige nicht zwingend erforderlich), da über diese die Speicherung der „Nutzungsdaten“ realisiert wird.

public class ReportRun extends ObjectRun
{
   InventDimParm inventDimParm;
   DialogRunbase dialog;
   DialogGroup dialogInventoryDimensions;

   #define.CurrentVersion(1)
   #localmacro.CurrentList
      inventDimParm
   #endmacro
}

void updateDesign()
{
   ;
   InventDimCtrl::updateReportVisible(element, inventDimParm);
}

public void run()
{
   ;
   this.updateDesign();
   super();
}

public Object dialog(Object _dialog)
{
   ;
   dialog = _dialog;
   dialogInventoryDimensions = inventDimParm.addFieldsToDialog(dialog,"@SYS53654",true, false, "@SYS102592");
   return dialog;
}

public boolean getFromDialog()
{
   ;
   inventDimParm.getFromDialog(dialog, dialogInventoryDimensions);
   return true;
}

public container pack()
{
   return [#CurrentVersion, #CurrentList];
}

public boolean unpack(container packedClass)
{
   Version version = RunBase::getVersion(packedClass);
   ;
   switch(version)
   {
      case #CurrentVersion:
         [version,#CurrentList] = packedClass;
         break;
      default:
         return false;
   }
   return true;
}

Wird nun der Bericht geöffnet, zum Beispiel über ein MenuItem, kann in einem Dialog ausgewählt werden, welche Lagerdimensionen auf dem Report angedruckt werden sollen.

Der ausgegebene Bericht(Report) sieht, unter Berücksichtigung der im Dialog gewählten Einstellungen, wie folgt aus.

Das vorgestellt Bespiel steht hier als Download bereit um die einzelnen Schritte genau ansehen/nachvollziehen zu können.

AKU_Demo_InventDimRep.rar (1,99 KB)
Saturday, April 04, 2009 8:24:53 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Friday, March 13, 2009

Im Standard von Microsoft Dynamics AX besteht auf jeder Maske, auf der Artikel und deren Lagerdimensionen angezeigt werden, die Möglichkeit, die Lagerdimensionen, bzw. die angezeigten Felder der Lagerdimensionen, über die Funktion "Lager-Dimensionenanzeige" entsprechend zu steuern.

Die einzelnen Elemente (Felder) der Lagerdimension können über diese Funktion ein- bzw. ausgebledet werden.

Weiterhin ist es auch möglich, durch Parametrisierung zu bestimmen, ob ein Feld einer Lagerdimension eingeben werden muss (Mussfeld) oder ob überhaupt eine Eingabe möglich ist.

Ein gutes Beispiel hierfür ist die Maske "Aufträge".

Wie ist es nun, wenn eine neue Maske erstellt werden soll, welche Artikelinformation und Lagerdimensionen anzeigen soll?
Wie genau muss vorgegangen werden, um die bereits an vielen Stellen im Standard verwendete Funktionalität auch für die selbst erstellte Maske bereitzustellen? 

Gehen wir einmal davon aus, es wurde eine neue Tabelle erstellt, welche die Artikelnummer (ItemId) und die Lagerdimensionsnummer (InventDimId) speichert.
Für diese Tabelle soll eine Maske erstellt werden, úm dem Benutzer die Möglichkeit zu geben, Datensätze zu erfassen, zu ändern oder einfach nur anzuzeigen.

Dies könnte z.B. so aussehen:

Um nun die Funktion der Lagerdimensionensteuerung einzubauen muss zuerst die Tabelle InventDim als DataSource der Maske hinzugefügt werden.
Anschließend müssen die Eigenschaften (Properties) der DataSource noch auf folgende Werte geändert werden.

Name

InventDim

JoinSource

Haupt-Datenquelle (hier: AKUDemoTable)

LinkType

InnerJoin

DelayActive

No

InsertAtEnd

No

InsertIfEmpty

No

Nun muss eine neue ButtonGroup (Name: "Inventory") im Designzweig der Maske erstellt werden. Diese sollte das LAbel "Lager" zugewiesen werden.
Nun noch das MenuItem "InventDimParmFixed" in diese ButtonGroup ziehen (z.B. per drag & drop aus dem AOT) und dem so erstellten MenuItemButton folgende Eigenschaften zuweisen.

Name

InventDimParmFixed

MenuItemName

InventDimParmFixed

DataSource

Haupt-Datenquelle (hier: AKUDemoTable)

Über das MenuItem (oder genauer über den erstellten MenuItemButton) wird nun wie im Standard, die Maske "Lagerdimensionen" zu öffnen.

Allerdings öffnet sich die Maske „Lagerdimensionen“ noch nicht wie gewünscht über den MenuItemButton. Hierfür sind noch weitere Anpassungen notwendig.

Damit sich die Maske „Lagerdimensionen“ wie gewünscht öffnet muss die neue Maske die Methode „inventDimSetupObject“ implementieren welche eine Instanz von „InventDimCtrl_Frm“ zurück gibt.

Die Klasse „InventDimCtrl_Frm“, bzw. eine der von ihr abgeleiteten Klassen, steuert z.B. welche Lagerdimensionen für den aktuellen Datensatz zulässig sind oder welche Dimensionen für den aktuellen Datensatz angegeben werden müssen, damit dieser gespeichert werden kann.

Da über die Parametrisierung der Lagersteuerungsgruppen und der Modulparameter hierfür durchaus unterschiedliche Einstellungen gewählt werden können, sind in Dynamics AX etliche Ableitungen dieser Klasse vorhanden (jede wird für eine oder mehrere andere Masken verwendet).

Je nachdem, was für eine Funktionalität bzw. was für ein Business-Prozess erstellt werden soll, kann entweder eine der bereits im Standard vorhandenen Klassen verwendet werden oder es muss eine neue Klasse geschrieben werden, um die benötigte Funktionalität zu liefern (z.B. welche Dimensionen immer angezeigt werden müssen).

Das Erstellen einer neuen Klasse, welche von „InventDimCtrl_Frm“ abgeleitet  ist, ist recht einfach.

Es sollte immer die Methode „new“ überschrieben werden und mindestens die statischen Methoden „construct“ und „newFromForm“ erstellt werden.

class AKUInventDimCtrl_Frm_Demo extends InventDimCtrl_Frm
{
}

protected void new()
{
  super();
}

public static AKUInventDimCtrl_Frm_Demo construct()
{
  return new AKUInventDimCtrl_Frm_Demo();
}

static AKUInventDimCtrl_Frm_Demo newFromForm(FormRun _formRun)
{
  AKUInventDimCtrl_Frm_Demo inventDimCtrl = AKUInventDimCtrl_Frm_Demo::construct();
  InventDimAxFormAdapter adapter = InventDimAxFormAdapter::newFromForm(_formRun);
  ;
  inventDimCtrl.parmCallingElement(adapter);
  inventDimCtrl.init();
  return inventDimCtrl;
}

Weiterhin können noch andere Methoden überschrieben werden, um z.B. zu steuern, welche Felder der Tabelle „InventDim“ beim Aufruf der Maske angezeigt werden sollen.
Weitere Informationen hierzu sind im Microsoft Dynamics AX Developer Center zu finden: http://msdn.microsoft.com/en-us/library/cc618009.aspx

NoYes mustShowGridField(fieldId _dimFieldId)
{
  NoYes ret;

  ret = super(_dimFieldId);
  //always show InventLocationId in Grid
  if(_dimfieldId == fieldnum(InventDim, InventLocationId))
  {
    ret = NoYes::Yes;
  }

  return ret;
}

Wie zuvor beschrieben muss nun die Methode „inventDimSetupObject“ auf der Maske implementiert werden. Das diese eine Instanz von „InventDimCtrl_Frm“ zurück geben muss, ist diese ebenfalls zu erzeugen. Als erstes muss eine Objektvariable für das „InventDimCtrl_Frm“ Objekt erstellt werden.

public class FormRun extends ObjectRun
{
  InventDimCtrl_Frm inventDimFormSetup;
}

Anschließend kann die Methode „inventDimSetupObject“ erstellt werden.

Object inventDimSetupObject()
{
  return inventDimFormSetup;
}

Da die Objektvariable durch diesen Quelltext noch nicht initialisiert wird, muss noch entsprechender Code zur Initialisierung geschrieben werden.

void updateDesign(InventDimFormDesignUpdate mode)
{
  inventDimParm inventDimParmShow;
  inventDimParm inventDimParmEnabled;
  ;
  switch (mode)
  {
    case InventDimFormDesignUpdate::Init :
      if (!inventDimFormSetup)
      {
        inventDimFormSetup = AKUInventDimCtrl_Frm_Demo::newFromForm(element);
      }
      inventDimFormSetup.parmSkipOnHandLookUp(true);

    case InventDimFormDesignUpdate::Active :
      inventDimFormSetup.formActiveSetup(
      inventTable::find(AKUDemoTable.ItemId).dimGroupId);
      inventDimFormSetup.formSetControls(true);
      break;

    case InventDimFormDesignUpdate::FieldChange :
      inventDimFormSetup.formActiveSetup(
      inventTable::find(AKUDemoTable.ItemId).dimGroupId);
      inventDim.clearNotSelectedDim(inventDimFormSetup.parmDimParmEnabled());
      inventDimFormSetup.formSetControls(true);
      break;

    default :
      throw error(strfmt("@SYS54195",funcname()));
  }
}

public void init()
{
  ;
  super();
  element.updateDesign(InventDimFormDesignUpdate::Init);
}

Da die Logik, welche durch die Methode „updateDesign“ bereit gestellt wird, mehrfach benötigt wird, erfolgt die Initialisierung des „InventDimCtrl_Frm“ Objekts nicht direkt in der „init“ Methode.

Damit die in der Maske erstellten Datensätze auch richtig gespeichert werden können, müssen nun noch einige weitere Anpassungen an den Methoden der Datenquellen vorgenommen werden.

Datasource „AKUDemoTable“ (Hauptdatenquelle):
Hier sind die Methoden „write“, „validateWrite“ und „active“ zu überschreiben.

public void write()
{
  ;
  ttsbegin;

  AKUDemoTable.inventDimId = InventDim::findOrCreate(InventDim).inventDimId;

  super();

  if(AKUDemoTable.inventDimId != InventDim.inventDimId)
  {
    InventDim.data(InventDim::find(AKUDemoTable.inventDimId));
    InventDim_ds.setCurrent();
  }

  ttscommit;
}

public boolean validateWrite()
{
  boolean ret;
  ;
  AKUDemoTable.InventDimId = inventDim::findOrCreate(InventDim).inventDimId;

  ret = super();
  return ret;
}

int active()
{
  int ret;
  ;
  ret = super();

  element.updateDesign(InventDimFormDesignUpdate::Active);

  inventDim_DS.active();

  return ret;
}

Datasource „InventDim“:
Hier müssen die Methoden „initValue“ und „write“ überschrieben werden.

public void initValue()
{
  ;
  InventDim.data(InventDim::find(AKUDemoTable.inventDimId));

  super();
}

public void write()
{
  //super();
}

Hierbei muss unbedingt beachtet werden, dass der „super“ Aufruf in der „write“ Methode der Datasource „InventDim“ auskommentiert wird, um das Speichern von falschen InventDim Datensätzen zu verhindern.

Als letzte Methode sollte nun noch die Methode „modified“ des DataSource-Field „ItemId“ der Datasource „AKUDemoTable“ überschrieben werden, damit auf eine Änderung der Artikelnummer reagiert werden kann (z.B. Artikelabhängige Anzeige der Lagerdimensionen).

public void modified()
{
  ;
  super();
  element.updateDesign(InventDimFormDesignUpdate::FieldChange);
}

Somit sind alle benötigten Quelltextanpassungen durchgeführt, sodass nur noch die Feldgruppe „InventoryDimensions“ der DataSource „InventDim“ mit in das Grid gezogen werden muss um die Lagerdimensionen auf der Maske anzuzeigen. Optional kann diese auch in die TabPage „Dimensions“ gezogen werden um ein standardkonformes Aussehen der Maske zu erhalten.

Das vorgestellt Bespiel steht hier als Download bereit um die einzelnen Schritte genau ansehen/nachvollziehen zu können.
AKU_Demo_InventDimFrm.rar (2,71 KB)

Friday, March 13, 2009 6:50:59 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Wednesday, March 11, 2009

Leider scheint es ein Problem mit duplizierten Tabellen in Dynamics AX 2009 zu geben.
Unter gewissen "Umständen" ist es möglich, Quelltext der in einem andern Layer (z.B. CUS oder BUS) geschrieben wurde, in den SYS Layer zu "verschieben".

Dieser Verhalten ist sehr unschön, da viele Entwickler, z.B. für Testzwecke, mal ein Objekt duplizieren, neuen Quelltext testen und anschließen diesen auf das orginale Objekt übernehmen oder verschieben. Leider taucht genau an dieser Stelle das Problem auf (es kann sein, dass dieses "Verschieben" den Quelltext in den SYS Layer schreibt).

Das genaue Verhalten ist in einem Video von "elranu" auf YouTube beschrieben (Link zum Video):

In den Newsgroups ist auch ein entsprechender Thread zu finden: Ax 2009 bug in Sys Layer

Wednesday, March 11, 2009 9:22:18 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Monday, March 02, 2009

Oft ist in Newsgroups und Foren die Frage zu lesen, wie .NET (CLR) Arrays in X++ verwendet, bzw. wie diese deklariert werden können.
Im Großen und Ganzen unterscheidet sich die Syntax für die Verwendung eines .NET Array kaum von der eines reinen X++ Array.
.NET Arrays können in X++ sogar auf zwei verschiedene Arten deklariert werden.

Variante 1:

Die Deklaration eines .NET Arrays erfolgt analog zu der Deklaration eines "reinen" X++ Arrays:

System.Object[] arrayOfObjects;
System.Int32[] arrayOfIntegers;

Die Syntax für die Instanzierung des .NET Array weicht allerdings leicht von der "normalen" X++ Syntax ab:

arrayOfObjects = new System.Object[10]();
arrayOfIntegers = new System.Int32[3]();

Wichtig ist hierbei, dass immer "()" verwendet wird.

Um die Werte eines .NET Arrays zu setzen wird die Methode "SetValue()" verwendet:

arrayOfObjects.SetValue(myObject, 0);
arrayOfIntegers.SetValue(300, 1);

Um Werte aus einem Array abzufragen kann die Methode "GetValue" verwendet werden:

arrayOfObjects.GetValue(0);
arrayOfIntegers.GetValue(1);

Eine weitere sehr nützliche Methode ist die Methode "get_Length()". Diese liefert die Anzahl der Array-Elemente zurück.

Variante 2:

Alternativ zur ersten Variante besteht noch die Möglichkeit, ein .NET Array über die Klasse System.Array zu erzeugen.
Leider stößt man bei diesem Weg immer mal wieder auf kleinere Probleme, weswegen die erste Variante für die Verwendung von .NET Array bevorzugt werden sollte.

Eine etwas ausführlichere Beschreibung der Verwendung von .NET Array in X++ bzw. deren besonderheiten und Abweichungen zur "normalen" X++ Syntax kann im Microsoft Dynamics AX Developer Center gefunden werden.

How to: Use X++ Syntax for CLR Arrays

Monday, March 02, 2009 8:40:39 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


Wie in einem Artikel auf der Microsoft Dynamics AX Webseite zu lesen ist wird der "alte" COM Business Connector nicht mehr in zukünftigen Versionen von Dynamics AX enthalten sein.
The COM Business Connector will be discontinued in future releases of Microsoft Dynamics AX

Bereits in der Version 2009 von Microsoft Dynamics AX wird der COM Business Connector vom "normalen" Setup nicht mehr angeboten.
Dieser muss manuell, wie im Microsoft Dynamics AX Developer Center beschrieben, nachinstalliert werden.
How to: Install COM Business Connector using Command-line Options

Somit ist es an der Zeit, bestehende Lösungen welche den COM Business Connector verwenden, auf den neueren .NET Business Connector zu portieren, um diese Lösungen auch in zukünftigen Versionen verwenden zu können.

Alle benötigten Informationen über die Verwendung des .NET Business Connectors können in der Library des Microsoft Dynamics AX Developer Centers gefunden werden
.NET Business Connector Overview

 

Monday, March 02, 2009 8:21:05 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Saturday, February 07, 2009

Wer schon mit dem AIF in der Version 4.0 von Microsoft Dynamics AX gearbeitet hat wird sich daran erinnern, dass ein Debuggen des Quellcodes, welcher durch das AIF ausgeführt wird, nur möglich ist, wenn hierfür eine kleine Codeanpassung in den Klassen "AifInboundProcessingService" und "AifOutboundProcessingService" vorgenommen wird.
Das genaue Vorgehen für die Version 4.0 von Dynamics AX ist in diesem Artikel beschrieben.

Für Dynamics AX 2009 kann diese Quellcodeänderung allerdings nicht so ohne weiteres angewendet werden, da für Dynamics AX 2009 einige Features ergänzt wurden (z.B. paralelle Verarbeitung von AIF Nachrichten) und somit der Quellcode der beiden Klassen einige Abweichungen zu dem der Version 4.0 hat.

Debuggen von ausgehenden Nachrichten

Um das Debuggen von ausgehenden Nachrichten zu ermöglichen, muss die Methode "runAsWrapper" der Klasse "AifOutboundProcessingService" angepasst werden.
Der Aufruf von "runAS" (Zeile 22) muss durch "AifOutboundProcessingService::processAsUser(messageIdContainer)" ersetzt werden.

...
try

{
   // runAs currentUser and process all messages in the container.
   new RunAsPermission(runAsUserId).assert();

   // AKU, Enable Debuging for outbound messages - START -->
   // Do not use in production system!!!
   // BP deviation documented
   //runas(runAsUserId,
   // classnum(AifOutboundProcessingService),
   // staticmethodstr(AifOutboundProcessingService, processAsUser),
   // messageIdContainer,
   // runAsCompany);
   AifOutboundProcessingService::processAsUser(messageIdContainer);
   // AKU, Enable Debuging for ourbound message - END -->

   // Revert the permission
   CodeAccessPermission::revertAssert();
}
...

Debuggen von eingehenden Nachrichten

Um das Debuggen von eingehenden Nachrichten zu ermöglichen, muss die Methode "runAsWrapper" der Klasse "AifInboundProcessingService" angepasst werden.
Der Aufruf von "runAS" (Teile 24) muss durch "AifInboundProcessingService::processAsUser(messageIdContainer)" ersetzt werden.

...
try
{
   // Convert to Axapta UserId
   axaptaUserId = AifEndpointUser::getAxaptaUser(runAsUserId).Id;

   new RunAsPermission(axaptaUserId).assert();

   // AKU, Enable Debuging - START -->
   // Do not use in production system!!!
   // BP deviation documented
   //runas(axaptaUserId,
   // classnum(AifInboundProcessingService),
   // staticmethodstr(AifInboundProcessingService, processAsUser),
   // messageIdContainer);
   AifInboundProcessingService::processAsUser(messageIdContainer);
   // AKU, Enable Debuging - END -->

   CodeAccessPermission::revertAssert();
}
...

Für beide Quellcodeänderung sollte noch erwähnt werden, dass diese in einem Produktivsystem nicht durchgeführt werden sollten, da dies Auswirkungen auf die Verarbeitung der Stapelprozesse des AIF's haben könnte.

Saturday, February 07, 2009 12:29:08 AM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Tuesday, January 27, 2009

Das Application Integration Framework von Dynamics AX basiert auf Dokumenten (Axd<Document> Klassen).
Eigene Dokumente lassen sich reicht einfach per Hand oder mit Hilfe des Dokumenten-Wizards erstellen.
In der Version 2009 von Dynamics AX erstellt dieser Wizard auch gleichzeitig den benötigenten Service (WCF-Webservice) und andere benötigte Elemente wie (Serviceklassen und Macros).
Welche Schritte hierfür benötigt werden ist zum Beispiel im Microsoft Dynamics AX Developer Center beschrieben.

Ein kleines Problem entsteht, wenn das neue Dokument, genauer gesagt die Elemente oder Objekte des Dokuments, in einem Layer (zum Beispiel CUS-Layer) entwickelt wird und später, aus welchen Gründen auch immer, alle Objekte des Dokuments (Query, Ax<Table> Class, Axd<Document> Class) in einen anderen Layer (zum Beispiel VAR-Layer) verschoben werden.

Nach der "Verschiebung" des neuen Dokuments in einen anderen Entwicklungslayer werden durch die AIF-Konfigurationsmasken (siehe Maske Dienstleistungen) allerdings keine Operationen (Insert, Update, Delete, FindKey, etc.) mehr angezeigt.
Auch an anderen Stellen, wie zum Beispiel der Endpunktkonfiguration, sind die entsprechenden Operationen nicht mehr auswählbar oder vorhanden.

Der Grund hierfür liegt in der ClassId der Serviceklasse des neuen Dokuments. Dieser, wie auch jedem anderen Objekt, wird beim Import in einen anderen Layer eine neue ID zugewiesen, wenn nicht explizit angegeben wurde, dass der Export und Import mit ID's erfolgen soll. So kann es sein, dass die Klasse mit der ID 40001 nach dem Import in einen anderen Layer die ID 300001 zugeordnet ist.

Da wärend der Konfiguration des AIF's der AOT nach Dokumenten/Services durchsucht wird und für jedes Dokument bzw. jeden Service ein Datensatz in der Tabelle "AifService" sowie ein bis mehrere Datensätze in der Tabelle "AifAction" erzeugt wird, welche alle eine Referenz auf die ClassId der Serviceklasse enthalten, kommt es jetzt zu dem genannten Problem.
Der Id 40001 ist nun keine Klasse oder noch schlimmer eine andere Klasse zugewiesen. Auch eine "Aktualisierung" dieser Datensätze über die Aktualisierungsfunktion, welche auf der Maske Dienstleistungen bereit gestellt wird, führt nicht zum gewünschten Erfolg.

Genau in dieser Funktion scheint sich ein kleiner Fehler eingeschlichen zu haben. Dort wird zwar eine Aktulisierung der ClassId für die Datensätze der Tabelle "AifService", aber nicht für die Datensätze der Tabelle "AifAction", durchgeführt.

Um diese Verhalten zu reproduzieren, muss folgendes gemacht werden:

  1. Erstellen eines neuen AIF Dokuments bzw. AIF Services.
  2. Über die Maske Dienstleistungen, zu finden unter "Grundeinstellungen -> Einstellungen -> Application Integration Framework -> Dienstleistungen", Funktion "Aktualisieren" das neue Dokument / den neuen Service "aktivieren".
  3. Über den Button "Servicearbeitsgänge" können nun alle Operationen welche durch das Dokument / den Service bereit gestellt werden eingesehen werden.
  4. Verschieben aller Elemente des Dokuments / des Services in einen anderen Layer.
  5. Schritt 2 erneut druchführen.
  6. Über den Button "Servicearbeitsgänge" werden keine Operation mehr angezeigt.

Um dieses Problem zu lösen, gibt 2 Möglichkeiten.

Entweder manuelles Löschen alle zu diesem Dokument/Service gehörigen Datensätze der Tabelle "AifAction" oder aber man ergänzt die Methode "registerOperations" der Klasse "AifServiceGenerationManager" um ein wenig X++ Code (bei Zeile 43) welcher nicht nur den Namen der Operation aktualisiert sondern auch die ClassId.
Da dieser Code sehr einfach ist verzichte ich an dieser Stelle auf ein Beispiel.

Leider tritt dieses Problem auch mit Dynamics AX 2009 Service Pack 1 auf.

Tuesday, January 27, 2009 7:46:19 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Thursday, January 08, 2009

In Microsoft Dynamics AX werden alle Informationsmeldungen, Warnungen und Fehlermeldungen in einem Fenster, dem so genannten Infolog, ausgegeben.

Wie Informationen, Warnungen oder Fehlermeldungen erzeugt werden können, ist an vielen Stellen bereits beschrieben.
Ein wie ich finde sehr guter Artikel über dieses Thema ist dieser: The user friendly Infolog.

An dieser Stelle soll aber kurz beschrieben werden, wie die Meldungen des Infologs ausgewertet werden können, um zum Beispiel zu ermitteln, ob das Infolog eine Fehlermeldung oder auch eine Warnung enthält.

Zuerst stellt sich die Frage, warum benötigt man überhaupt diese Art von Information da in Dynamics AX die Möglichkeit besteht, verschiedenste Operationen innerhalb einer Transaktion zu Kapsel und diese bei Auftreten eines Fehlers oder Erzeugung einer Fehlermeldung (Stichwort "throw error") automatisch rückgängig zu machen.

Diese Frage soll anhand eines Beispiels beantwortet werden.

Angenommen wir möchten eine Anpassung in Dynamics AX schreiben, welche es ermöglicht, Änderungen an Stücklisten und Arbeitsplänen von Produktionsaufträgen zu automatisieren und alle getätigten Änderungen in einer Transaktion zu kapseln. Anschließend soll noch der Status der Produktions aktualisiert werden. Ebenfalls innerhalb der Transaktion.
Zum Beispiel soll der Produktionsauftrag gestartet werden.

Hierfür ist es notwendig verschiedene Standardfunktionen von Dynamics AX zu verwenden, die bereits eine Fehlerbehandlung implementiert haben und somit keine Fehler mehr "melden", welche den Abbruch einer Transaktion zu Folge haben könnten.
Somit wird zwar gewährleistet, dass alle "Unterfunktionen" in sich richtig auf Fehler richtig reagieren, aber dennoch könnte eine Dateninkonsistenz entstehen, da nicht alle Operationen rückgängig gemacht werden. Es könnte Beispielhaft sein, dass die Anlage von neuen Stücklistenpositionen und/oder Arbeitsgangpositionen funktioniert, die spätere Statusaktualisierung des Produktionsauftrags aber nicht. Die erstellten Stücklistenpositionen und/oder Arbeitsgangpositionen würden im System gespeichert (bleiben).

Abhilfe für dieses Problem kann duch die Auswertung des Infologs und manuellen Aufrufs von "ttsabort" geschaffen werden.

Mit dem "SysInfologEnumerator" können alle Meldungen, welche in das Infolog geschrieben wurden, durchlaufen werden.
Über die Methode "currentException" kann anschließend ausgewertet werden, um was für eine Meldung (Information, Warnung, Fehler) es sich handelt.

Hier ein kurzes Beispiel, wie dies aussehen könnte:

boolean hasError()
{
   SysInfologEnumerator enum;
   SysInfoAction action;
   boolean hasError = false;
   ;
   //Analyse the infolog to see if there are any warnings/errors
   enum = SysInfologEnumerator::newData(infolog.infologData());
   while (enum.moveNext())
   {
      switch (enum.currentException())
      {
         case Exception::Error:
         case Exception::Warning:
            hasError = true;
            break;
      }

      infolog.add(enum.currentException(), enum.currentMessage(), enum.currentHelpUrl());
   }
   return hasError;
}

Es muss allerdings beachtet werden, dass alle Meldungen des Infologs, durch den Aufruf von "SysInfologEnumerator::newData(...)", gelöscht werden.
Sollen diesese Meldungen nach der "Auswertung" dennoch dem Benutzer angezeigt werden, müssen diese wieder manuell in das Infolog geschrieben werden (über "infolog.add(..)").

Thursday, January 08, 2009 7:09:12 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Tuesday, December 09, 2008

Im Standard von Microsoft Dynamics AX 2009 werden verschiedenste Business Intelligence Funktionen mitgeliefert.
Die hierfür benötigen Cubes und Dimensionen erstellt Dynamics AX 2009 unter Verwendung der Analysis-Extension direkt in den Analysis Services des SQL Servers.
Dies ist schon einmal sehr schön, da die meiste Arbeit durch die Installationsroutinen abgenommen wird.

Muss oder möchte man allerdings die mitgelieferten Cubes oder Dimensionen an die eigenen Gegebenheiten anpassen, muss zuerst ein BI-Projekt für Visual Studio erzeugt werden, da die Bearbeitung in Visual Studio erfolgt (über die Funktion „BI-Projekt generieren“).

Es können einige „allgemeine“ Einstellungen für die Erstellung eines BI-Projektes über die Funktion „Generierungsoptionen für BI-Projekt“ getätigt werden.
Beispielhaft sei hier die Einstellung der Zeitdimensionen genannt.

Allerdings kann es sein, dass man anstatt der erwartet Maske eine Fehlermeldung ausgegeben bekommt.
Dies kann besonders bei Verwendung, der für Dynamics AX 2009 bereit gestellten, Demo Daten der Fall sein.

Leider ist diese Fehlermeldung nicht sehr Aussagekräftig.

Hier hilft ein Blick in das Ereignisprotokoll des Dynamics AX Object Servers.

Dies lässt zumindest schon einmal den „groben“ Grund erkennen, was für ein Fehler verursacht wurde bzw. wo der Fehler liegen könnte.

Nach einem Blick in die Tabellendefinition und den Tabellebrowser der angegebenen Tabelle „BIUDMROLES“ wird man feststellen, dass es die angegeben Spalte wirklich nicht gibt. Es gibt aber einen Datensatz mit entsprechender UserGroupId (UserGroupId = PRComplete). Verwendet man nun die Funktion „Gehe zu Haupttabelle“, wird man feststellen, dass es diese Benutzergruppe nicht im System gibt.

Somit ist die Lösung recht einfach.
Nachdem der Fehlerhafte Datensatz gelöscht wurde, kann die Maske „Generierungsoptionen für BI-Projekt“ ohne Probleme geöffnet werden.

Tuesday, December 09, 2008 9:26:28 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Wednesday, December 03, 2008

Wie bestimmt man ein Alias für ein Tabellenfeld in Microsoft Dynamics AX um über mehr als ein Feld die Auswahl des Datensatzes zu bestimmen?

In den Auftragspositionen kann man anstelle einer Artikelnummer auch den Suchbegriff aus dem Artikelstamm eingeben um den gewünschte Artikel auszuwählen.

Beispiel:

Artikelnummer: 12345
Suchbegriff: MeinArtikel

Da, sofern man keine sprechenden Artikelnummern verwendet, ein Suchbegriff oft einprägsamer ist als eine Artikelnummer hat man Systemweit die Möglichkeit anstelle der Artikelnummer „12345“ den Suchbegriff „MeinArtikel“ einzugeben.

Diese Funktion kann man ganz einfach auch selbst erstellen. Hierzu wird auf Tabellenfeldebene in den Eigenschaften des Feldes Suchbegriff, die Eigenschaft AliasFor auf Artikelnummer (ItemId) gesetzt.




Sobald man nun „MeinArtikel“ im Feld Artikelnummer eingibt, wird automatisch die Artikelnummer eingefügt.
Dieses Verhalten ist nun Systemweit anwendbar. Überall wo ein Artikel über die Artikelnummer ausgewählt werden kann (Bspw. Aufträge, Bestellungen) kann nun in der manuellen Eingabe der Suchbegriff verwendet werden. Nach der Eingabe des Suchbegriffs wird dieser durch die Artikelnummer ersetzt (sofern vorhanden).

Wednesday, December 03, 2008 7:18:04 PM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [0] -


 Wednesday, November 05, 2008

Microsoft Dynamics AX 2009 bietet die Möglichkeit, den Verlauf eines Dokuments, welches über das AIF exportiert oder importiert wurde zu betrachten.
Dies war auch schon mit Microsoft Dynamics AX 4.0 möglich.

Über die Maske "Dokumentverlauf" können alle Dokumente/Nachrichten eingesehen werden, welche über das AIF verarbeitet wurden.
Über den Button "Korrelation" ist es sogar möglich, die von der Verarbeitung (schreiben, ändern, lesen, etc.) betroffenen Datensätze anzuzeigen.

So ist es zumindest in der Theorie.
In der Praxis sieht es leider etwas anders aus. Nach einem Klick auf den Button "Korrelation" öffnet sich leider nicht wie erwartet die Maske "Dokumentkorrelierung".
Stattdessen wird der Debugger (wenn installiert) geöffnet und die Fehlermeldung ausgegeben, dass ein Objekt nicht über die Methode "extendedTypeId" verfügt.

So wie es scheint, hat sich in den Quellcode ein kleiner Fehler eingeschlichen, welcher dazu führt, dass die Maske "Dokumentkorrelierung" niemals geöffnet werden kann.
Nach einem Vergleich der Funktionalitäten zwischen Dynamics AX 4.0 und Dynamics AX 2009 kann dieses Verhalten (der Fehler) aber wie folgt beschrieben behoben werden.

  1. AOT öffnen und zu der Tabelle "AifCorrelation" navigieren.
  2. Den Quelltext der Methode "displayEntityKey" anzeigen lassen bzw. diese für die Bearbeitung öffnen.
  3. Folgende Quelltextzeile suchen:
    dictField = new DictField(entityKey.parmTableId(), enumerator.currentKey());
  4. Dieses Zeile abändern in:
    dictField = new SySDictField(entityKey.parmTableId(), enumerator.currentKey());

Nach dieser kleinen Quelltextänderung sollte alles wie erwartet funktionierten und die Maske "Dokumentkorrelierung" mit den entsprechenden Datensätzen angezeigt werden.

Wednesday, November 05, 2008 7:06:49 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Sunday, October 12, 2008

Im wieder taucht in den Newsgroups und einschlägigen Foren die Frage auf, ob es möglich ist, Dynamics AX 2009 unter Windows Server 2008 und/oder in Verbindung mit SQL Server 2008 zu betreiben.

Die Antwort auf diese Frage lautet eigentlich „Ja“, zugleich aber auch „Nein“.

Offiziell sind die beiden Produkte zwar noch nicht für die Verwendung mit Dynamics AX 2009 freigegeben, aber prinzipiell funktioniert Dynamics AX 2009 auch mit dieser Systemkonfiguration (Kernfunktionalität).
Allerdings muss auch erwähnt werden, dass der eine oder andere Punkt bei der Installation bzw. beim Betrieb von Dynamics AX 2009 mit Windows Server 2008 und/oder dem SQL Server 2008 für Verwirrung sorgen kann.

So wird, nach erfolgreicher Installation der Basiskomponenten von Dynamics AX 2009 und anschließendem AOS Start, eine Fehlermeldung im Ereignisprotokoll von Windows Server 2008 erzeugt, welche aussagt, dass Dynamics AX 2009 (genauer der AOS) das gewählte Betriebssystem nicht unterstützt.

Ungeachtet dieser Fehlermeldung, läuft der AOS Dienst von Dynamics AX 2009 unter Windows Server 2008 ohne weitere Probleme. Unschön ist nur, dass diese Meldung bei jedem Start des AOS erzeugt wird.

Für die Verwendung des Enterprise Portals bzw. des Rolecenters muss beachtet werden, dass wie im Installation Guide von Dynamics AX 2009 beschrieben, die Sharepoint Services 3.0 mit SP1 verwendet werden müssen, da frühere Versionen nicht richtig unter Windows Server 2008 laufen.

Ein weiterer Punkt der unbedingt beachtet werden sollte, sind die Reporting-Erweiterungen von Dynamics AX 2009.
Eine Installation der Reporting-Erweiterungen ist derzeit leider nur möglich, wenn die Reporting Services des SQL Server 2005 in der Service Pack Version 2 verwendet werden.
Sollen die Reporting Services des SQL Server 2008 verwendet werden, scheitert es schon an der Installation der Reporting-Erweiterungen von Dynamics AX 2009.
Diese lassen sich in einer solchen Systemumgebung erst gar nicht installieren. Das Setup wird durch eine entsprechende Fehlermeldung abgebrochen.

Dies hat zur Folge, dass die Reporting Services des SQL Server 2008 nicht mit Dynamics AX 2009 verwendet werden können.

Gleiches gilt für die Analysis Extensions von Dynamics AX 2009 in Kombination mit den Analysis Services des SQL Server 2008.
Diese lassen sich zwar ohne Problem installieren, aber eine Verarbeitung der Cubes ist nicht möglich, da diese auf Grund von Verarbeitungsfehlern abgebrochen wird.

Schlussendlich bedeutet dies, dass die Verwendung von Windows Server 2008 als Betriebssystem für Dynamics AX 2009 keine Probleme bereiten sollte.

Für die reinen Datenbankdienste von SQL Server 2008 trifft dies ebenfalls zu. In meinen Test konnte ich keinerlei Probleme beim Betrieb mit Dynamics AX 2009 erkennen.
Anderes gilt für die Reporting und Analysis Services von SQL Server2008. Deren Verwendung ist leider noch nicht möglich und es müssen weiterhin die Reporting und Analysis Services des SQL Server 2005 verwendet werden um alle möglichen Funktionalitäten von Dynamics AX 2009 zur Verfügung stellen zu können.

Sunday, October 12, 2008 4:43:26 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Friday, October 03, 2008

Im Microsoft Partner Portal stehen die Präsentationsfolien der Vorträge, die beim Dynamics AX Technical Airlift 2008 sowie dem Dynamics ERP-Launch, gehalten wurden zum download bereit.

Die Präsentationsfolien können über folgenden Link gedownloaded werden.
Microsoft Dynamics ERP-Launch und Technical Airlift - Vortragsfolien

 

Friday, October 03, 2008 4:14:32 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Friday, September 12, 2008

Am 9. September 2008 fand der Dynamics Technical Airlift 2008 in Fürstenfeldbruck bei München statt. Wie auch schon im letzten Jahr, richtete sich die Veranstaltung an die eher technisch ausgerichteten Personen (Consultants/Entwickler) aus der Dynamics-Gemeinde.

Ich selbst durfte in diesem Jahr als ATE (Ask the Expert) an dieser Veranstaltung teilnehmen.
Der eine oder andere wird mich in dem orangen Poloshirt gesehen haben. :-)

Insgesamt kann ich nur sagen, es war eine sehr gut organisierte Veranstaltung, auch wenn einige Vorträge, für den einen oder anderen, nicht die gewünschte technische Tiefe hatten.
Auch der gemeinsame Informationsaustausch und die vielen Gespräche mit Personen der Dynamics-Gemeinde haben die Veranstaltung positiv abgerundet.

Da Meinungen ja bekanntlich weit auseinander gehen, möchte ich jeden einzelnen bitten, seine Eindrücke und Meinungen zu dem Dynamics Technical Airlift 2008 zu schildern.
Wer dies nicht "öffentlich", durch die Kommentarfunktion (hier), machen möchte, kann mir auch gerne eine Email schreiben (Email me).

Ich möchte auch die jenigen bitten, die nicht an dieser Veranstalltung teilgenommen haben, mir ein kurzes "Feedback" zukommen zu lassen.
Besonders die Erwartungen an eine solche Veranstaltung und/oder die Community im Allgemeinen würden mich interessieren.

Bitte beachtet, dass alle Kommentare erst durch mich "überprüft" werden müssen, bevor sie angezeigt werden.

Friday, September 12, 2008 5:45:45 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Saturday, August 30, 2008

Alle frei erhältlichen Dokumente über Microsoft Dynamixs AX 2009 stehen jetzt über Windows Live SkyDrive für jedermann frei zur Verfügung.

Vielen Dank an Arijit Basu, der sich die Arbeit gemacht hat, diese dort zum downlaod zur Verfügung zu stelllen. 

Mehr Informationen hierzu gibt es im Blog von Arijit Basu.

Hier der "direkt" Link zu den Dokumenten: AX 2009 Documents

Saturday, August 30, 2008 3:53:37 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


Wie bereits im diesem Artikel "Fehlermeldung beim Starten des Microsoft Dynamics AX Clients" beschrieben, kann es zu Fehlermeldungen beim Starten des Dynamixs AX 4.0 Client kommen.

Eine weitere Fehlermeldung, welche erzeugt werden kann ist "Incompatible ext. version".
Es ist auch möglich, dass diese sogar mehrfach ausgegeben wird.

Grund hierfür ist meist ein Problem mit der TAPI-Integartion des CRM Moduls, bzw. genauer gesagt, ein Problem mit den eingestellten Wählregeln/Standorte der Windows Telefon- und Modemoptionen.

Die Behebung des Fehlers ist eigentlich ganz einfach.

  1. Wenn die TAPI-Integration nicht genutzt wird, kann diese deaktiviert werden.
    Wie dies genau geht kann in diesem Artikel "Fehlermeldung beim Starten des Microsoft Dynamics AX Clients" nachgelesen werden.

  2. Wenn die TAPI-Integration verwendet werden soll, muss ein neuer Standort in den Windows Telefon- und Modemoptionen erstellt werden.
    Das Erstellen eines neuen Standorts erfolgt über "Start -> Systemsteuerung -> Telefon- und Modemoptionen" auf dem jeweiligen Clientcomputer (pro Benutzer).
    Dort sollte, wenn vorhanden, ein bestehender Standort gelöscht werden und ein neuer angelegt werden.
Saturday, August 30, 2008 2:39:27 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Thursday, August 14, 2008

Wie bereits in diesem Artikel "Auswahl von mehreren Datensätzen in einem Grid-Control (MultiSelect)" beschrieben, kann für ein Grid-Control die Eigenschaft MultiSelect gesetzt werden, womit es ermöglicht wird, dass mehrere Datensätze für eine weitere Verarbeitung ausgewählt werden können.

Dies Funktioniert solange, bis <DataSource>_ds.research() aufgerufen wird. Dieser Aufruf hat zur Folge, dass die Daten der DataSource neu geladen werden und somit auch die Selektierung verworfen wird.

Ein Beispiel wie es nicht funktioniert:

void clicked()

   CustTable custTable; 
   ; 
   for (custTable = CustTable_ds.getFirst(true) ? CustTable_ds.getFirst(true) : CustTable_ds.cursor(); 
        custTable; 
        custTable = CustTable_ds.getNext()) 
   { 
      //do something with custTable 
      info(custTable.accountNum);
      
custTable_ds.research();    
   }
}

Es gilt also genau zu beachten zu welchem Zeitpunkt bzw. an welcher Stelle im Quelltext die Methode <DataSource>_ds.research() aufgerufen wird.

Weiterhin kann es zu Problemen beim MultiSelect kommen, wenn in den Methoden der DataSource ein Aufruf von <DataSource>_ds.research() erfolgt.
Normalerweise werden die DataSource-Methoden für jeden selektierten Datensatz ausgeführt. Wenn aber innerhalb einer der Methoden, wie z.B. Delete(), wird diese Methode nur für den ersten ausgewählten Datensatz ausgeführt und dann ein Research ausgeführt, was wie schon beschrieben zu einem Verwerfen der Selektierung führt.

Thursday, August 14, 2008 8:00:44 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Friday, August 08, 2008

Microsoft Dynamics AX verwendet für eindeutige Kennungswerte (Id’s) die eingebauten Nummernkreise, für welche ein Feld vom Typ „String“ benötigt wird. Dies macht auch Sinn, da Nummernkreise in Dynamics AX oft ein oder mehrere alphanumerische Zeichen enthalten. Natürlich können auch rein nummerische Nummernkreise mit diesem „Framework“ erstellt werden.

Allerdings sind die Nummernkreise im Dynamics AX Standard nicht ganz optimal bei der Verwendung von einem rein nummerischen Nummernkreisen. Dies fängt z.B. schon beim Datentyp an, der für das ID-Feld der Tabelle verwendet werden muss. Bedingt dadurch, dass ein Feld vom Typ „String“ verwendet werden muss, belegt dieses Feld unnötig viel Speicher in der Datenbank. Weiterhin gestalten sich Sortierungen, Rechenoperationen, etc. erheblich schwieriger.

Diese Probleme können umgangen werden, wenn für das ID-Feld der Datentyp „Integer“ oder „Int64“ verwendet wird. Leider kann nun nicht mehr das Nummernkreis-Framework des Dynamics AX Standards verwendet werden, da dies den Datentyp „String“ für ein ID-Feld vorschreibt.

Es muss also ein eigenes, kleines Nummernkreis-Framework oder ein eigener Nummernkreis geschrieben werden, der die Verwendung des Datentyps „Integer“ für ID-Felder ermöglicht. Dies hört sich zuerst schwierig an, da Dinge wie fortlaufende Nummernvergabe oder die Wiederverwendung von freien Nummern (Löchern im Nummernkreis) berücksichtigt werden sollten.

Es ist aber ganz und gar nicht schwierig, eine eigene Nummernkreisfunktionalität zu erstellen. Das einzige was hierfür benötigt wird ist eine entsprechen aufgebaute Select-Abfrage.

Die nächste Nummer eines Nummernkreises ist immer die zuletzt vergebenen Nummer (höchste) + 1.

   Aku_TestTable t1;
   ;
   select maxof(ID) from t1;
   return t1.ID + 1;

Etwas schwieriger wird es, wenn auch die frei gewordenen Nummern des Nummernkreises wieder vergeben/verwendet werden sollen. Dann muss immer die kleinste Id aus der Tabelle gesucht werden, für die es keinen Datensatz in der Tabelle gibt. Gibt es kein "Nummernloch", muss die nächst höchste Nummer vergeben werden.

   Aku_TestTable t1;
   Aku_TestTable t2;
   ;
   select minof(ID) from t1 notexists join t2 where t2.ID == (t1.ID + 1);
   return t1.ID + 1;

Um diese ein wenig zu verdeutlichen, kann eine beispielhafte Implementierung eines eigenen Nummernkreises in diesem Demoprojekt "SharedProject_AKU_OwnIntNumberSeq" angesehen und runter geladen werden.

Friday, August 08, 2008 5:38:44 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Wednesday, July 16, 2008

Die Benamung von Tabellenfeldern erfolgt über Labels, die in der Feldeigenschaft "Label" festgelegt werden.
Diese Benamung wird immer angezeigt, wenn das Feld in der Applikation angezeigt wird. Es gibt in den Eigenschaften der Tabellenfelder noch die Eigenschaft "GroupPrompt", die die Benamung in Masken festlegt, wenn das Feld innerhalb einer Gruppe angezeigt wird.

Die hinterlegte Benamung in der Eigenschaft GroupPrompt wird immer dann verwendet, wenn das Feld in einer Gruppe dargestellt wird. Das können Tabellenfeldgruppen oder Gruppen, die in Forms erzeugt wurden, sein.

Wednesday, July 16, 2008 7:28:11 AM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [0] -


 Thursday, July 10, 2008

Manchmal ist es möglich, dass beim Starten des Microsoft Dynamics AX 4.0 Clients eine oder meherer Fehlermeldungen in einem Infolog-Fenster ausgegeben werden.

Diese Fehlermeldung könnten z.B. "Corrupted ini file" sein.

Die große Frage ist nun, woher kommt diese Fehlermeldung bzw. wodurch wird diese erzeugt.
Leider ist die Fehlermeldung, welche im Ereignisprotokoll gefunden werden kann, meist auch nicht besonders hilfreich.

Sollten solche, eher unerklärlichen Fehlermeldungen beim Starten des Dynamics AX Client ausgegeben werden, lohnt sich oft ein Blick in die Systemkonfiguration, welche über Verwaltung, Einstellungen, System, Konfiguration aufgerufen werden kann.

Der Grund für diese Fehlermeldung könnte die aktivierte Telefonieintegration des CRM Moduls sein.
Wird diese deaktiviert, sollte die Fehlermeldung nicht mehr erzeugt werden.

Thursday, July 10, 2008 7:13:05 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Thursday, June 26, 2008

In der Screencast Sektion von Channel 9 hat Mey Meenakshisundaram 6 Screencasts über die Entwicklung mit dem Enterprise Portal veröffentlicht.

Microsoft Dynamics AX 2009 – Creating simple List Page in Enterprise Portal
This how-to video demonstrates how to create a simple List Page in Enterprise Portal using AxGridView control.

Microsoft Dynamics AX 2009 – Creating simple Task Page in Enterprise Portal
This how-to video demonstrates how to create a simple Task Page in Enterprise Portal using AxForm control.

Microsoft Dynamics AX 2009 – Creating simple Tunnel(Wizard) Page in Enterprise Portal
This how-to video demonstrates how to create a simple Tunnel(Wizard) Page in Enterprise Portal using ASP.net Wizard control and EP AxForm control.

Microsoft Dynamics AX 2009 – Calling X++ classes in Enterprise Portal User Controls in C#
This how-to video demonstrates how to create a simple X++ Class in AOT and create a C# proxy for this class and call it in Enterprise Portal user control written in C#.

Microsoft Dynamics AX 2009 – Using Record Context in Enterprise Portal
Microsoft Dynamics AX developers will learn how to use record context in Enterprise Portal. Record context is used to pass currently selected record information on a page or Web part to another page or connected Web part. This how-to-video demonstrates three ways of passing record context...

Microsoft Dynamics AX 2009 – Advanced Grid in Enterprise Portal List Page
This how-to video demonstrates how to add ranges in dataset to restrict the data displayed and the different options supported (open,hidden,locked). This also demonstrates how to use display and edit methods defined in the table in the Grid.

Thursday, June 26, 2008 6:46:58 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Thursday, June 12, 2008

Wenn bei der Entwicklung mit Microsoft Dynamics AX 4.0 die Quellcodeverwaltung mittels Visual SourceSafe 2005 eingeschaltet wurde, besteht die Möglichkeit einzelne Versionen eines Objekts miteinander zu vergleichen.

Hierbei kann es aber bei einer "ungünstigen" Konfiguration des lokal Repository-Verzeichnisses sein, dass bei einem Vergleich von zwei Objektversionen die Fehlermeldung "Fehler: Fortsetzen nicht möglich" ausgegeben wird.

Diese Fehlermeldung wird immer erzeugt, wenn sich das lokale Repository-Verzeichnis und das Verzeichnis, in dem die temporären Internetdateien (Temporary Internet Files) gespeichert werden, nicht auf der gleichen Partition (Datenträger) befinden.

Beispiel:

Ordner der Temporary Internet Files = C:\Dokumente und Einstellungen\UserXY\Lokale Einstellungen\Temporary Internet Files
Ordner des lokalen Repositories = D:\VSSRepository\Test

-> Die Fehlermeldung wird ausgegeben.

Ordner der Temporary Internet Files = C:\Dokumente und Einstellungen\UserXY\Lokale Einstellungen\Temporary Internet Files
Ordner des lokalen Repositories = C:\VSSRepository\Test

-> Die Fehlermeldung wird nicht ausgegeben und der Versionvergleich funktioniert problemlos.

Dieses Problem wird durch ein Update für Visual SourceSafe 2005 behoben. Es empfiehlt sich, bei Verwendung der Quellcodeverwaltung mit Visual SourceSafe 2005 als VC-System, dieses Update einzuspielen.

Thursday, June 12, 2008 12:57:09 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Wednesday, June 11, 2008
Die Eigenschaften von FormControls, wie heightMode und widthMode werden im Form Designer über Enums gesetzt. Leider hat man hier mittels X++ Probleme, denn es wird bei der Verwendung dieser Methoden nicht angegeben um welchem Enum es sich handelt. Das hat zufolge, dass man immer wieder probieren muss, um die gewünschte Eigenschaft richtig zu setzten. Desweiteren ist so auch der Quellcode schlechter lesbar.

Der Aufruf sieht normal so aus:

FormControl.heightMode(1);

Besser ist es, wenn man sprechende Parameter mit dem richtigen Enum verwendet

FormControl.heightMode(FormHeight::ColumnHeight);

Mögliche Optionen:

FormHeight::Auto
FormHeight::ColumnHeight


Ähnlich sieht der Parameter bei widthMode aus, nur wird hier der Enum FormWidth genutzt.

FormControl.witdthMode(FormWidth::ColumnHeight);

Mögliche Optionen:

FormWidth::Auto
FormWidth::ColumnHeight


Wie schon bei den letzten beiden Eigenschaft kann man bei TopMode auch nur raten, was mittels X++ übergeben werden muss.

FormControl.TopMode(0);

Besser ist, wenn auch hier ein sprechender Parameter mit dem richtigen Enum verwendet wird.

FormControl.TopMode(FormTop::Auto);

Mögliche Optionen:

FormTop::Auto
FormTop::ButtomEdge
FormTop::Center
FormTop::TopEdge

Bei FormGroupControls gibt es die Eigenschaft frameOptionButton, auch hier tritt dasselbe Problem auf.

FormControl.frameOptionButton(0);

Der Enum lautet hier FormFrameOptionButton und ermöglicht somit die korrekte Zuweisung oder besser lesbaren Quellcode zu schreiben.

FormControl.frameOptionButton(FormFrameOptionButton::None);

Mögliche Optionen:

FormFrameOptionButton::None
FormFrameOptionButton::Hide
FormFrameOptionButton::Check
FormFrameOptionButton::Radio

Es kommt nicht nur bei FormControls zu dieser Art von Problemen. Bei QueryRanges gibt es die Eigenschaft Status, auch hier kann es beim Setzen zu Schwierigkeiten dieser Eigenschaft mittels X++ kommen.

QueryBuildRange.Status(0);

Besser ist auch hier den sprechenden Parameter mit dem Richtigen Enum zu verwenden.

QueryBuildRange.Status(RangeStatus::Hidden);

Mögliche Optionen:

RangeStatus::Hidden
RangeStatus::Locked
RangeStatus::Open

Das waren erstmal die Sachen, die mir noch in Erinnerung sind. Es gibt wahrscheinlich einige andere Stellen, bei denen es Schwierigkeiten bereitet, den richtigen Parameter zu wählen.
Ich habe mit den genannten Enums bisher positive Erfahrungen sammeln können, ob es aber wirklich die Richtigen sind, wird man wohl nicht in Erfahrung bringen können.

Wednesday, June 11, 2008 7:50:59 PM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [0] -


 Friday, June 06, 2008

So nach und nach erscheinen immer mehr Informationen über die Version 2009 von Dynamics AX im Internet.
Hier eine Liste der bereits verfügbaren Quellen:

Install Microsoft Dynamics AX 2009 (Informationen zur Installtion von Dynamics AX 2009)

Using Microsoft Dynamics AX 2009 (Allgemeine Informationen zu Dynamics AX 2009)

Microsoft Dynamics AX 2009 SDK

What's new for Microsoft Dynamics AX 2009 (Änderungen/Neuerungen als download)

The Microsoft Dynamics AX Enterprise Portal Blog (Informationen über das EP, direkt vom MS EP Team)

 

 

Friday, June 06, 2008 2:30:27 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Thursday, May 29, 2008

Für jede Tabelle können Systemfelder wie Erstellt von, Geändert von, Erstellungsdatum, Erstellungszeit oder Änderungsdatum von Dynamics AX aktiviert werden.
Diese Felder werden durch Dynamics AX automatisch gefüllt. Wird zum Beispiel ein neuer Datensatz erzeugt, füllt Dynamics AX die Systemfelder mit den entsprechenden Daten.

Es gibt aber Situationen wo man selber Einfuß auf die Werte dieser Felder nehmen muss. Ein Beispiel hierfür könnte eine Datenübernahme sein, bei der die Informationen über den Ersteller oder das Erstellungsdatum des Datensatzes nicht verloren gehen dürfen.

Wie dies gehen kann zeigt dieses kleine Beispiel:

YourTable table;
;
ttsbegin;

//can only be called on server tier. -> method must be executed on server tier.
new SkipAOSValidationPermission().assert();
table.skipAosValidation(true);

table.YourField = "Value";

table.overwriteSystemfields(true);

//set your own values for the system fields.
table.(fieldnum(Table1, ModifiedDate)) = today() - 2;
table.(fieldnum(Table1, CreatedDate)) = today() - 5;
table.(fieldnum(Table1, CreatedBy)) = "TEST";

table.insert();
ttscommit;

table.skipAosValidation(false);

Allerdings können die Systemfelder nur beim Erstellen eines neuen Datensatzes "von Hand" festgelegt werden.

Wie das Ändern von Werten der Systemfelder bei bereits bestehenden Datensätzen geht demonstriert die Klasse "BatchRun", Methode "runJob" und "finishJob".
Kurz gesagt wird genau genommen der Datensatz nicht geändert, sondern es werden nur die Daten des bestehenden Datensatzes in den neuen Datensatz kopiert (mit newBuffer = oldBuffer.data()) und dann wie bereits beschrieben die Systemfelder mit eigenen Werten befüllt. Dann wird der bestehnde Datensatz gelöscht und der neue Datensatz in die Datenbank geschrieben.

Thursday, May 29, 2008 10:36:51 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Monday, May 19, 2008

So nach und nach sind immer mehr Informationen über die nächste Version von Microsoft Dynamics AX im Internet erhältlich, die einen ersten Einblick in das verschaffen, was alles an Neuerungen mit Dynamics AX 2009 kommt.

Arijit Basu hat in seinem Blog einen Artikel über einige der Änderungen gepostet.
Der Artikel beschreibt unter Anderem die Änderungen am Benutzerinterface, das neue Role Center, die neuen Workflow-Features sowie die Erweiterungen am Enterprise Portal.

Weiterhin ist auf Channel9 ist ein Video zu finden, welches das Enterprise Portal von Dynamics AX 2009 und dessen System-Architektur erläutert.
Es wird gezeigt, wie eigene Controls (mit ASP.NET) für das Enterprise Portal erstellt werden können und wie diese in das Enterprise Portal sowie das Role-Center eingebunden werden können.

 

Monday, May 19, 2008 9:41:46 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Monday, May 05, 2008

Häufig werden Optionen (Ja/Nein-Fragen) in Microsoft Dynamics AX durch ein CheckBox Control und einem entsprechendem Feld einer Tabelle abgebildet.

Ein gutes Beispiel hierfür ist die Maske „Lagerparameter“, Reiter „Lagerungsdimensionen“. Hier kann eingestellt werden, welche Lagerungsdimension wo im System angezeigt werden soll. Zur Speicherung der gewählten Einstellungen wird die Tabelle „InventDimSetupGrid“ verwendet. Diese Tabelle enthält für jede Option jeweils ein Feld (abgeleitet vom Enum „NoYes“).
Diese Art der Speicherung von Optionswerten ist sicherlich sehr leicht zu verstehen und auch sehr einfach zu erstellen. Da aber für jede Option ein Feld in der Tabelle angelegt werden muss, kann dies relativ zeitaufwendig sein.

Es besteht aber die Möglichkeit, Optionswerte in nur einem Feld zu speichern. Diese Art der Speicherung kann unter Umständen sogar als die elegantere angesehen werden, da z.B. für einen Datensatz weniger Speicher in der Datenbank benötigt wird.

Um dies zu realisieren, wird als erstes ein Feld vom Typ „int“ in der Tabelle benötigt. Dieses Feld dient als Datenspeicher für alle benötigten Optionswerte (Ja oder Nein). Jedes Bit dieses „int“ Feldes stellt genau einen Optionswert und somit eine Option dar. Um nun die gewählten Werte der Optionen speichern zu können, müssen diese mit Bit-Operationen (right / left shift, binary and, etc.) in das „int“ Feld geschrieben werden.


Das Feld "bitMask" wird in dieser Darstellung als Datenspeicher der Optionswerte verwendet.


Auf einer Maske werden alle Optionen als einzelne CheckBoxen bereit gestellt.

Da diese Art der Speicherung in Dynamics AX nicht besonders oft verwendet wird und Quellcode oft mehr sagt als (nur) ein langer Artikel, habe ich ein kleines „Tutorial“ erstellt, um die benötigten Schritte zu beschreiben.

SharedProject_AKU_EnumControl_Frm.rar (2,03 KB)

Eine Erklärung aller Bit-Operatoren ist im Microsoft Dynamics AX Developer Center zu finden.

Monday, May 05, 2008 8:53:48 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Friday, May 02, 2008

Im Microsoft Dynamics AX Developer Center wurde für Dynamics AX Entwickler eine neue Webcast Serie gestartet.

Auszug:

"On this page you will find videos designed for all Microsoft Dynamics AX developers, from the novice to the professional. New videos are added regularly, so check back often."

Derzeit ist nur ein Webcast über "Dynamics Links between parent and child Forms" erhältlich.
Gilt zu hoffen, dass in der nächsten Zeit noch weitere nützliche Webcast folgen.

Friday, May 02, 2008 4:06:10 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Monday, April 28, 2008

Wie kann man zur Laufzeit den FormControlType einer beliebigen FormControl ermitteln?

Eine Methode auf der FormControl scheint nicht zu existieren, wie kann also der Typ einer FormControl mittels X++ ermittelt werden?
Mit Hilfe der Klasse SysFormRun ist dies möglich.

Hierzu ein kleines Stück Quellcode als Beispiel.

FormControlType formControlType;
;
formControlType = SysFormRun::controlType(classidget(MeineFormBuildControl));

Sollte keine FormControl als Parameter übergeben werden, wir eine Fehlermeldung generiert.
Nun kann entsprechend des ermittelten Types der FormControl weiter verfahren.




Monday, April 28, 2008 9:10:53 PM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [0] -


 Monday, April 21, 2008

Eine Listbox ist einfach erstellt. Entweder man benutzt eine datengebundene Listbox, indem einfach die Datasource und DataField/ DataMethod im FormControl festgelegt wird oder man befüllt die Listbox manuell.

Das manuell befüllen der Listbox aus einem Tabellenfeld sieht dann beispielsweise so aus:

void initListBox()
{
counter elementCnt;
str elementStr;
;
meineListBox.clear();
for (elementCnt = 1; elementCnt <= conlen(Tabelle.ContainerFeld); elementCnt++)
{
meineListBox.add(onpeek(Tabelle.ContainerFeld, elementCnt));
}
}

Über die angegebene Methode werden alle Elemente des aktuellen Containers der Tabelle in die Listbox übertragen.

Hierbei bin ich auf ein seltsames Verhalten der Listbox gestossen.
Im aktuellen Fall ist die Darstellung der Listbox einwandfrei. Beim Datensatz wechsel habe ich die Listbox mit den neuen Werten befüllen können. Alles war gut.

Bis auf die Tatsache, dass ich bei der Auswahl in der Listbox nicht das ausgewählt Element angezeigt bekam. Es wurde immer das erste angezeigte Element der ListBox gewählt.

Grund hierfür war oder ist: Meine Listbox befindet sich in einer FormGroupControl, bei der FormGroupControl war automatisch eine DataSource hinterlegt. Das hat scheinbar zu Folge, dass das Event "SelectionChanged" der Listbox ignoriert wurde und ich somit immer der erste Wert über

meineListBox.getText(meineListBox.selection());

erhalten habe.

Nachdem die Datasource aus der FormGroupControl entfernt wurde, funktionierte alles wieder wie erwartet und gewünscht.


 

Monday, April 21, 2008 9:10:36 AM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [0] -


 Monday, April 14, 2008

Um die FormDataSource einer Form anzusprechen benutzt man das Suffix _ds. So erhält man in der Maske CustTable mit

CustTable_ds

den Zugriff auf die FormDatasource der CustTable.

Es gibt aber noch andere Suffixe, die nicht so häufig verwendet werden und mir bis dato auch unbekannt waren.
So erhält man mit dem Suffix _q oder _qr Zugriff auf die Query oder QueryRun der aktuellen FormDataSource.

Das heisst, das mit

CustTable_q

die Query der FormDataSource CustTable und mit

CustTable_qr

die QueryRun der FormDataSource CustTable direkt angesprochen wird.

Hiermit kann man sich also verschiedene Deklarationen oder Zuweisungen sparen und hat direkten Zugriff auf diese Objekte.

  • FormDataSource = Zugriff auf den Datensatzpuffer
  • FormDataSouceName_DS = Zugriff auf FormDataSource
  • FormDataSourceName_Q = Zugriff auf Query der FormDataSource
  • FormDataSourceName_QR = Zugriff auf QueryRun der FormDataSource
Monday, April 14, 2008 9:52:06 AM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [0] -


 Saturday, April 12, 2008

Das Erstellen von Produktionsaufträgen sollte an sich kein Problem darstellen. Leider ist dem nicht ganz so.

Anders als in anderen Modulen (z.B. Aufträge) von Dynamics AX, existiert hierfür keine Klassenstruktur, welche die entsprechenden Funktionen bereit stellt.
Der Dynamics AX Standard erstellt Produktionsaufträge immer über die Maske „ProdTableCreate“. Es gibt aber Situationen, wo für die Erstellung eines Produktionsauftrages keine Maske verwendet werden kann. Ein Beispiel hierfür könnte eine Schnittstelle sein, welche über eine Textdatei die zu produzierenden Waren einließt und entsprechende Produktionsaufträge im System generiert.

Die Frage ist nun, wie erstellt man Produktionsaufträge per Quellcode, damit diese auch „richtig“ im System erzeugt werden (inkl. Stückliste, Arbeitsplan und Lagerbuchung).

  • Zuerst muss der Produktionsauftrag mit den Daten des zu produzierenden Artikels initialisiert werden.
  • Weiterhin müssen Produktionsmenge und Lieferdatum festgelegt werden.
  • Ebenfalls sind die zu verwendende Stückliste und der Arbeitsplan zu definieren.
  • Und als letzter Schritt muss der Produktionsauftrag noch erzeugt werden.

Hierbei gilt es aber zu beachten, dass die Erstellung (Speichern in der Datenbank) nicht mit der Methode „insert“ der Tabelle „ProdTable“ geschieht, sondern dass hierfür die Klasse „ProdTableType“ und deren Methode „insert“ verwendet wird. Nur so wird die entsprechende Lagerbewegung / Lagerbuchung im System erzeugt und wenn notwendig Referenzen zu einem Verkaufsauftrag oder einer anderen Produktion hinterlegt.

Um dies zu veranschaulichen ein kurzes Beispiel, in welchem ein neuer Produktionsauftrag erstellt wird.

static void CreateProductionOrder(Args _args)
{
    //Die zu produzierende Menge
    ProdQtySched productionQty = 1;
    //Der zu produzierende Artikel
    ItemId productionItem = "Artikelnummer";

    ProdTable prodTable;
    InventTable inventTable;
    ;
    inventTable = InventTable::find(productionItem);

    //Initialisierung des Produktionsauftrags
    prodTable.initValue();
    prodTable.ItemId = inventTable.ItemId;
    prodTable.initFromInventTable(inventTable);

    //Lieferdatum festlegen
    prodTable.DlvDate = today();

    prodTable.QtySched = productionQty;
    prodTable.RemainInventPhysical = prodTable.QtySched;

    //Die zu verwendende Stückliste und Arbeitsplan bestimmen
    prodTable.initRouteVersion();
    prodTable.initBOMVersion();

    //Produktionsauftrag erstellen
    prodTable.type().insert();
}

Selbstverständlich sind auch weitere Angaben bei der Erstellung des Produktionsauftrags möglich.
Z.B. kann ein Produktionsauftrag auch aus einer Verkaufsauftragsposition erzeugt werden (bei Verwendung der Methode "initFromSalesLine").

Saturday, April 12, 2008 4:30:59 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Friday, April 04, 2008

Mit Hilfe von SysDictClass kann man wertvolle Erkenntnisse über Klassen gewinnen und abfragen.
Ich habe einige Dinge, die ich am häufigsten gebrauche, anhand einfacher Beispiele zusammengetragen.

Dieses kleine Stück Quellcode ermittelt alle abgeleiteten Klassen der Klasse FormControl.

SysDictClass formCtrlClss;
ListEnumerator listEnumerator;
;

formCtrlClss = new SysDictClass(classnum(FormControl));

listEnumerator = formCtrlClss.extendedBy().getEnumerator();

while (listEnumerator.moveNext())
{
formCtrlClss = new SysDictClass(listEnumerator.current());
print formCtrlClss.name();
}
pause;

Diese Funktionalität wird auch im Standard von Microsoft Dynamics AX verwendet, um alle möglichen Funktionen der Stabelverarbeitung abzufragen/ zu ermitteln.

Man kann natürlich auch ermitteln, von welcher Klasse(n) die aktuelle Klasse abgeleitet ist.

SysDictClass dictClass = new SysDictClass(classnum(SalesFormLetter_Invoice));
;
while (dictClass.extend())
{
dictClass = new SysDictClass(dictClass.extend());
print dictClass.name();
}
pause;

Um zu ermitteln, ob eine Klasse von einer bestimmten Klasse abgeleitet wurde, muss folgendes geschrieben werden.

SysDictClass dictClass = new SysDictClass(classnum(SalesFormLetter_Invoice));
;
// Überprüfen ob die Klasse von der Klasse Object abgeleitet ist
print dictClass.isExtending(classnum(Object));
// Überprüfen ob die Klasse von der Klasse FormControl abgeleitet ist
print dictClass.isExtending(classnum(FormControl));
pause;

Es kann auch auf die Methoden der Klasse zugegriffen werden.

SysDictClass dictClass = new SysDictClass(classnum(SalesFormLetter_Invoice));
;
//Statische Methode der Klasse SalesFormLetter_Invoice aufrufen, wenn vorhanden
if (dictClass.hasStaticMethod("Description"))
print dictClass.callStatic("Description");

//Methode der Klasse SalesFormLetter_Invoice aufrufen, wenn vorhanden
if (dictClass.hasObjectMethod("canGoBatchJournal"))
print dictClass.callObject("canGoBatchJournal", dictClass.makeObject());

pause;

Das ist nur ein Teil der Funktionen, die ich persönlich am Wichtigsten empfinde und hin und wieder benötige.

Friday, April 04, 2008 9:01:19 PM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [0] -


 Thursday, March 27, 2008

In Foren, Newsgroups oder auch in persönlichen Gesprächen ergibt sich oft die Frage, wo man anfangen soll/kann um den Umgang mit Microsoft Dynamics AX zu lernen.

Gerade für Einsteiger oder Anfänger ist es machmal schwer, die Informationen zu finden, die gerade benötigt werden. Dank der Dynamics AX Community, welche zum Glück immer größer und besser wird, stellt das Internet mit seinen vielen Dynamics AX Blogs, Foren, Newsgroups und nicht zuletzt dem Microsoft Dynamics AX Developer Center eine sehr gut Informationsquelle dar.

Trotzdem hört man oft die Frage, ob es denn keine Bücher über Microsoft Dynamics AX gibt. Um etwas mehr Klarheit zu schaffen, welche Bücher über Microsoft Dynamics AX erhältlich sind, hier eine Liste aller Bücher die mir bekannt sind:

Thursday, March 27, 2008 8:00:07 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


 Wednesday, February 27, 2008

Ich habe die Erfahrung gemacht, dass FormControls, die zur Laufzeit direkt auf dem Design der Form erzeugt wurden, sich meisst am rechten Rand befinden.

Sollen diese FormControls nun aber nicht am Rechten, sondern am Linken Rand erscheinen, kann man dieses natürlich schon beim Erzeugen dieser Controls mittels

FormGroupControl.leftMode(FormLeft::LeftEdge) 

festlegen.

Die Probleme treten dann auf, wenn mehrere FormControls erzeugt werden und diese am Linken Rand erscheinen sollen.

Werden nun alle FormControls mit FormControl.leftMode(FormLeft::leftEdge) an den Linken Rand verschoben, erhält man dann unter Umständen erstaunliche Ergebnisse – nur nicht die Gewünschten.

Mit Hilfe der Methode

FormControl.moveControl(int _controlId [, int _insertAfterId = 0])

kann jedes Control an eine beliebige Stelle verschoben werden. Der Übergabeparameter ist hier die ID des zu verschiebenen FormControls. Diese Methode ist nur bei Container Controls, wie FormGroupControl, FormGridControl oder auf dem Design der Form verfügbar.

Wird nur 

ÜbergeordneteFormControl.moveControl(ControlIDderzuveschiebenenControl)

verwendet, wird das Control nur nach links (an die erste Position des Übergeordneten Controls) verschoben.

Soll das FormControl hinter einem bestimmten FormControl innerhalb desselben übergeordneten FormControls platziert werden, wird

Übergeordnete.moveControl(ControlIDderzuveschiebenenControl, ControlIDhinterDerdieControlangefügtwerdensoll)

benutzt.

Mit .moveControl() können auch FormControls in das übergeordnete Control eingefügt werden, die vorher nicht in dem übergeordneten Control enthalten waren. 

Anbei eine einfache Form, die schon ein FromGroupControl mit zwei Controls enthält,  bei der zur Laufzeit zwei neue Controls hinzugefügt werden und die mittels Button nach links verschoben werden.

FormDynamicControlsMove.zip (2,55 KB)
Wednesday, February 27, 2008 8:40:13 PM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [3] -


 Tuesday, February 12, 2008

In Kürze soll ein neues Buch über Microsoft Dynamics AX erscheinen. Im Schwerpunkt soll es sich mit dem Thema Qualitätssicherung beschäftigen. Titel des Buchs ist "Quality Assurance for Dynamics AX-Based ERP Solutions".

Ein Auszug der einzelnen Themenpunkte:

  • Customization Best Practices backed by theory
  • Learn rapidly how to test Dynamics AX applications
  • Verify Industry Builder Initiative-compliance of ERP software
  • Get ready-made testing templates
  • Code, design, and test a quality Dynamics AX-based ERP solution

     

    Genaueres über das Buch kann man hier erfahren.

  • Tuesday, February 12, 2008 9:10:36 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


     Saturday, January 26, 2008

    Das Buchen von Bestellungen in Microsoft Dynamics AX geschieht über die Klasse „PurchFormLetter“ bzw. einer ihrer konkretisierten (abgeleiteten) Klassen. Jeder Buchungstyp (z.B. Bestätigung oder Rechnung) ist durch eine eigene Klasse abgebildet, welche von der Basisklasse „PurchFormLetter“ abgeleitet ist (siehe Abbildung).

    Abbildung 1 - Klassenhierarchie der Klasse „PurchFormLetter“

    Vergleicht man die Klassenhierarchie der „PurchFormLetter“ Klassen mit der Klassenhierarchie der „SalesFormLetter“ Klassen, so ist zu erkennen, dass auch das Buchen von Bestellungen vom Prinzip her genau so funktioniert wie das Buchen von Aufträgen (Vergleiche hierzu: Microsoft Dynamics AX API – Teil 3 „Buchen von Aufträgen“).

    Deswegen sind auch für das Buchen von Bestellungen im Wesentlichen nur zwei Schritte notwendig.

    1. Über die Methode „construct“ der Klasse „PurchFormLetter“ ein dem Buchungstyp einsprechendes Objekt erzeugen.
    2. Über den Aufruf der Methode „update“ die Bestellung buchen.

    Hierzu ein Beispiel (Buchen des Lieferscheins für eine Bestellung):

    static void PurchPostPackingSlip(Args _args)
    {
       PurchFormLetter purchFormLetter;
       PurchTable purchTable;
       PurchId purchId;
       Num packingSlipId;
       ;
       //Angabe der Bestellung, für welche der Lieferschein gebucht werden soll.
       purchId = "00244_049";
       purchTable = PurchTable::find(purchId);

       //Bestimmen des Buchungstyps durch Angabe des DocumentStatus (Lieferschein).
       purchFormLetter = PurchFormLetter::construct(DocumentStatus::PackingSlip);

       //Festlegen der externen Lieferscheinnummer.
       packingSlipId = "EXT-100155L";

       //Buchen des Lieferscheins.
       purchFormLetter.update(purchTable,
                              packingSlipId,
                              SystemDateGet(),
                              PurchUpdate::All,
                              AccountOrder::Auto,
                              NoYes::No,
                              NoYes::No,
                              NoYes::No,
                              NoYes::No);
    }

    Einziger Unterschied zu den Auftragsbuchen ist, dass bei der Buchung einer Bestellung die „externe“ Nummer des Belegs (Lieferscheinnummer, Rechnungsnummer, etc.) angegeben werden muss.

    Analog zu den Auftragsbuchen, sind auch beim Buchen von Bestellungen umfangreichere oder etwas speziellere Buchungsszenarien möglich (Vergleiche hierzu: Microsoft Dynamics AX API – Teil 3 „Buchen von Aufträgen“).

    Saturday, January 26, 2008 4:12:18 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


     Thursday, January 17, 2008

    Alexei Eremenko hat auf seinem Blog einige Artikel über die kommende Version von Microsoft Dynamics AX und dessen neue Features veröffentlicht. Da diese leider in Russich geschrieben sind, hier eine kurze Zusammenfassung:

    • Aus Microsoft Dynamics AX 5.0 wird Microsoft Dynamics AX 2009.
    • Geänderte Benutzeroberfläche (Office 2007 Style, inkl. Ribbon's).
    • Neue "Funktion" Rollcenter, die dem Benutzer schnellen Zugriff auf die, für seine Arbeit, relevanten Daten geben soll.  
    • Unterstützung des UNION Befehl's für SQL Abfragen, bei Verwendung der Query-Klassen.
    • Neuer Exception-Typ "DublicateKeyException" zur Ausnahmebehandlung wenn ein Datensatz schon besteht.
    • Die SQL DML bulk Anweisungen erlauben die Verwendung von Inner- und Outer-Joins.
    • Zugriff auf das Ergebnis der "update_recordset" Anweisung, um zu bestimmen wie viele Datensätze durch die Operation geändert wurden.
    • CrossCompany-Unterstützung für Datenbankabfragen (Daten aus unterschiedlichen Mandaten können in einer SQL Anweisung behandelt werden).

    Wer die orginalen Artikel einmal selber lesen möchte, findet die einzelnen Blog-Posts hier:
    (Die Links verweisen auf die Übersetzung der Artikel ins Englische)

    Thursday, January 17, 2008 9:07:08 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


     Tuesday, January 15, 2008

    Wird in einem Bericht die Fetch Methode überschrieben sollte man drauf achten, dass die Standardfunktionen auch noch richtig funktionieren. Ein gutes Beispiel hier sind die Einstellungen im Seitenbereich.

     

    Wird in der Fetch Methode nicht super() aufgerufen und mit einer individuellen Abfrage gearbeitet, welche die Daten dem Bericht übergibt, werden die Einstellungen in der Gruppe "Seitenbereich" meist ignoriert. Es werden dann zwar nur die angebenden Seiten gedruckt, die Abfrage wird aber weiter durchgeführt. Das führt dann dazu, dass immer abgewartet werden muss bis die komplette Abfrage ausgeführt wurde, obwohl man evtl. nur einen kleinen Bruchteil der Daten (wie z.B. die erste Seite) benötigt.

    Das kann man ganz einfach verhindern indem in der Fetch Methode abgefragt wird, ob die Daten an den Bericht gesendet wurden. Hierzu wird die send(...) Methode benutzt.

    public boolean fetch()
    {
    Query queryBuild;
    QueryRun queryRunBuild;
    InventTrans iTrans;
    ;
    if (this.prompt())
    {
       queryBuild = this.query();
       queryRunBuild = new QueryRun(queryBuild);

       while (queryRunBuild.next())
       {
       iTrans = queryRunBuild.get(tablenum(InventTrans));

       // Wurde der aktuelle Datensatz ausgeben
       if (!this.send(iTrans))
          return true;
       }
    }
    return true;
    }

    Es muss also immer überprüft werden, ob die Daten gedruckt werden oder nicht. Das passiert mit:

    if (!this.send(meineDaten))
       return true;

    Tuesday, January 15, 2008 10:30:17 AM (Mitteleuropäische Zeit, UTC+01:00)  Mathias Füßler  #    Comments [0] -


     Wednesday, January 02, 2008

    Für ein Grid-Control kann über die Einstellung "MultiSelect" gesteuert werden, od dieses Control die Auswahl von mehr als einem Datensatz erlaubt.

    Gültige Einstellungen sind:
    Yes - Es können mehrere Datensätze ausgewählt werden.
    No - Es kann immer nur ein Datensatz ausgewählt werden.

    GridSelectedOneRecord
    Auswahl eines Datensatzes

    GridSelectedMultipleRecords
    Auswahl mehrerer Datensätze

    Zugriff auf die aktuelle Selektion (einer oder mehrere) erhält man wie folgt beschrieben:

    Ist nur ein Datensatz markiert, bzw. soll mit einfacher Auswahl gearbeitet werden (MultiSelect = No), kann der ausgewählte Datensatz über den aktuellen DataSource-Cursor der Grid-Control DataSource ermittelt werden.
    Der DataSource-Cursor steht immer auf dem zu letzt ausgewählten Datensatz eines Grid-Control's.

    Beispiel:

    void clicked()
    {
       //CustTable ist die DataSource des Grid-Controls 
       ;
       //Datenoperationen für den Datensatz ausführen.
       //Do something....
       info(CustTable.AccountNum);   

       //Angezeigte Datensätze im Grid Control aktualisieren
       element.lockWindowUpdate(true);
       CustTable_ds.research();
       element.lockWindowUpdate(false);
    }

    Soll eine Mehrfachauswahl möglich sein, reicht der Zugriff auf den aktuellen DataSource-Cursor nicht mehr aus. Um alle ausgewählten Datensätze der DataSource zu erhalten, muss diese mit einer Schleife unter Verwendung der Methoden "getFirst" und "getNext" durchlaufen werden. Hilfreich hierbei ist die Methode "anyMarked", mit welcher ermittelt werden kann ob mehrere Datensätze ausgewählt sind oder nicht.

    Auch hierfür ein Beispiel:

    void clicked()
    {
       CustTable selectedCustTable;
       Common currentRecord;
       ;
       if (CustTable_ds.anyMarked()) //Es sind meherer Datensätze selektiert.
       {
          //Ersten selektierten Datensatz ermitteln.
          selectedCustTable = CustTable_ds.getFirst(1);

          while(selectedCustTable)
          {
             //Datenoperationen für den Datensatz ausführn.
             //Do something....
             info(selectedCustTable.AccountNum);

             //Nächsten selektieren Datensatz ermitteln.
             selectedCustTable = CustTable_ds.getNext();
          }
       }
       else //Nur ein Datensatz ist selektiert.
       {
          //Selektierten Datensatz ermitteln.
          currentRecord = CustTable_ds.cursor().data();
          selectedCustTable = CustTable_ds.cursor();

          //Datenoperationen für den Datensatz ausführen.
          //Do something....
          info(selectedCustTable.AccountNum);
       }

       //Angezeigte Datensätze im Grid Control aktualisieren   
       element.lockWindowUpdate(true);
       CustTable_ds.research();
       CustTable_ds.findRecord(currentRecord);
       CustTable_ds.refresh();
       element.lockWindowUpdate(false);
    }

    Der Quellcode des Beispiels:

    Form_GridSelectedRecords.xpo (6.2 KB)

    Wednesday, January 02, 2008 10:39:57 PM (Mitteleuropäische Zeit, UTC+01:00)  Axel Kühn  #    Comments [0] -


    Über/Kontakt

      




    © Copyright 2012 Mathias Füßler
    Sign In
    Categories
    Archiv
    <October 2009>
    SunMonTueWedThuFriSat
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567
    Blogroll
    Statistik
    Total Posts: 107
    This Year: 0
    This Month: 0
    This Week: 0
    Comments: 19





    All Content © 2012, Mathias Füßler
    DasBlog theme 'Business' created by Christoph De Baene (delarou)