Dynamics AX Blog - Seite 26
AX 2012: Importieren eines Debitoren mit Hilfe des Data import/export frameworks
01.10.2013Microsoft Dynamics AX (Axapta)
|
AX 2012: Dialog "Der Modellspeicher wurde geändert" öffnet sich ständig
21.09.2013Microsoft Dynamics AX (Axapta)
SysCheckList_Update::finalizeMinorUpgrade();
Das ist sicherlich keine generelle Lösung, in meinem speziellen Fall hat mir das aber weitergeholfen. Update vom 15.01.2014 SysModelStoreModified::main(new Args());
|
AX 2012: Beispiel für den Einsatz einer Computed Column
07.09.2013Microsoft Dynamics AX (Axapta)
Dabei ist es wichtig zu verstehen, daß man bei einer Computed Column mit Hilfe von X++ SQL-Statements generiert. Im folgenden habe ich ein einfach gehaltenes Beispiel erstellt, um die Möglichkeiten zu demonstrieren. Szenario: Es soll ein View erstellt werden, der eine Spalte enthält wo die Artikelnummer des Artikel ausgegeben wird bzw. wenn bei dem Artikel eine sog. Ersatzartikelnummer eingetragen ist, soll diese statt dessen ausgegeben werden. |
Beispiel für einen Datumsfilter einer FormDataSourceIm folgenden ein Beispiel, wie man bei einer FormDataSource einen QueryBuildRange aufbaut, welcher nur tagesaktuelle Datensätze anzeigt. Im Beispiel enthält unsere FormDataSource namens DataSourceName zwei Datumsfelder namens FromDate und ToDate und es sollen abhängig von einer Checkbox nur Datensätze angezeigt werden, die "heute" gültig sind. public void applyFilter()
{ queryBuildRange qbr; qbr = sysQuery::findOrCreateRange(DataSourceName_ds.queryBuildDataSource(), fieldNum(DataSourceName, recId)); if( !ShowExpiredCheckBox.checked()) { qbr.value(strfmt('('+ '((%5.%2 <= %1) && (%5.%3 >= %1)) || ' + '((%5.%2 == %4) && (%5.%3 == %4)) || ' + '((%5.%2 <= %1) && (%5.%3 == %4)) || ' + '((%5.%3 >= %1) && (%5.%2 == %4)) ' + ')', Date2StrXpp(systemDateGet()), fieldstr(DataSourceName, FromDate), fieldstr(DataSourceName, ToDate), Date2StrXpp(dateNull()), tableId2name(tableNum(DataSourceName)))); } else { qbr.value(SysQuery::valueUnlimited()); } } Der Aufruf obiger Methode kann beispielsweise in der executeQuery() der Datasource erfolgen. |
AX 2012: Pflichtfeld-Hinweis von Registerkarten simulieren
17.08.2013Microsoft Dynamics AX (Axapta)
Dieses Feature kann man allerdings nicht bewusst steuern. Möchte man einen Register der keine "echten" Pflichtfelder enthält, dennoch als einen darstellen, in dem auszufüllende Felder enthalten sind, kann man folgenden Trick anwenden. Dazu muss man einfach in den Register ein StringEdit-Control einfügen und dessen AutoDeclaration-Property auf YES setzen. Anschließend kann man über folgenden Code beispielsweise das Control unsichtbar (Visible=False) schalten, gleichzeitig aber als Pflichtfeld kennzeichnen (Mandatory=True). StringEdit.mandatory(true);
StringEdit.visible(false); Dadurch blendet AX den roten Stern ein, gleichzeitig ist das auslösende Feld unsichtbar. Um die eigentliche Prüfung der Felder dieses Registers muss man sich selbstverständlich unabhängig von dieser Kennzeichnung trotzdem kümmern! |
X++ -Statement generieren um Datensätze anzulegen
13.08.2013Microsoft Dynamics AX (Axapta)
Vor kurzem hatte ich die Notwendigkeit einige Datensätze von einer AX-Umgebung in eine andere zu kopieren. Es ging um eine sehr flache Tabelle (d.h. ohne großartige Referenzen auf andere Tabellen oder Datensätze), wodurch ich folgenden Job dafür nutzen konnte. Der Job durchläuft ein Select-Statement (ganz am Ende des Jobs) und kopiert mir in die Zwischenablage ein X++-Statement, mit welchem man die Datensätze - beispielsweise über einen eigenen Job - einfügen kann. Inspiriert ist der Code übrigens von der Methode buildInsertScript() des Formulares SysRecordInfo. static void buildInsertScriptAX2009(Args _args)
{ CustGroup CustGroup; #define.indent(' ') SysDictTable dictTable; Counter fieldCounter; Counter arrayCounter; DictEnum dictEnum; DictField dictFieldTemp; DictField dictFieldArray; str fieldValue; TextBuffer textBuffer; Source source; boolean setFieldValue(fieldId _fieldId, common _common) { boolean ret = true; fieldValue = ""; if (dictFieldTemp.baseType() != Types::Container) { fieldValue = strfmt("%1", _common.(_fieldId)); } switch (dictFieldTemp.baseType()) { case Types::String : fieldValue = strReplace(fieldValue, '\n', '\\n'); fieldValue = '\"' + fieldValue + '\"'; break; case Types::Container : fieldValue = '[' + fieldValue + ']'; break; case Types::Enum : dictEnum = new DictEnum(dictFieldTemp.enumId()); fieldValue = dictEnum.name() + '::' + dictEnum.index2Symbol(_common.(_fieldId)); break; case Types::Real : fieldValue = num2str(_common.(_fieldId), 2, -1, 1, 0); break; case Types::Date : fieldValue = date2str(_common.(_fieldId), 123,2,4,2,4,4); if (fieldValue == '') { ret = false; } fieldValue = strReplace(fieldValue, '/', '\\'); break; case Types::UtcDateTime : fieldValue = DateTimeUtil::toStr(_common.(_fieldId)); if (fieldValue == '') { ret = false; } break; } return ret; } void loopRecord(common _common) { ; source += #indent + strfmt('%1.clear();\r\n', dictTable.name()); for (fieldCounter = dictTable.fieldNext(0); fieldCounter > 0; fieldCounter = dictTable.fieldNext(fieldCounter)) { dictFieldTemp = new DictField(dictTable.id(), fieldCounter); if (dictFieldTemp.isSystem()) continue; if (dictFieldTemp.arraySize() > 1) { arrayCounter = 1; while (arrayCounter <= dictFieldTemp.arraySize()) { dictFieldArray = new DictField(dictTable.id(), fieldId2Ext(dictFieldTemp.id(),arrayCounter)); if (setFieldValue(dictFieldArray.id(), _common)) { source = source + #indent + strfmt("%1.%2 = %3;", dictTable.name(), dictFieldArray.name(DbBackend::Native, arrayCounter), fieldValue) + '\r\n'; } arrayCounter++; } } else { if (setFieldValue(fieldCounter, _common)) { source = source + #indent + strfmt("%1.%2 = %3;", dictTable.name(), dictFieldTemp.name(), fieldValue) + '\r\n'; } } } source += #indent + strfmt('%1.insert();\r\n', dictTable.name()); } ; // Modify needed records here dictTable = new SysDictTable(tableNum(CustGroup)); while select CustGroup { loopRecord(CustGroup); } textBuffer = new TextBuffer(); textBuffer.setText(source); textBuffer.toClipboard(); info("@SYS87601"); } Das Ergebnis des Jobs ist in der Zwischenablage zu finden, und sieht beispielsweise wie folgt aus: CustGroup.clear();
CustGroup.CustGroup = "K-DRITT"; CustGroup.Name = "Kunden Drittland"; CustGroup.ClearingPeriod = ""; CustGroup.PaymTermId = ""; CustGroup.TaxGroupId = ""; CustGroup.PaymIdType = ""; CustGroup.insert(); CustGroup.clear(); CustGroup.CustGroup = "K-EU"; CustGroup.Name = "Kunden EU"; CustGroup.ClearingPeriod = ""; CustGroup.PaymTermId = ""; CustGroup.TaxGroupId = ""; CustGroup.PaymIdType = ""; CustGroup.insert(); CustGroup.clear(); CustGroup.CustGroup = "K-INL"; CustGroup.Name = "Kunden Inland"; CustGroup.ClearingPeriod = ""; CustGroup.PaymTermId = ""; CustGroup.TaxGroupId = ""; CustGroup.PaymIdType = ""; CustGroup.insert(); In Dynamics AX 2012 sieht der gleiche Job etwas anders aus: static void buildInsertScriptAX2012(Args _args) { CustGroup CustGroup; #define.indent(' ') SysDictTable dictTable; Counter fieldCounter; Counter arrayCounter; DictEnum dictEnum; DictField dictFieldTemp; DictField dictFieldArray; str fieldValue; TextBuffer textBuffer; Source source; boolean setFieldValue(common _common, FieldName _fieldName, ArrayIdx _arrayIndex = 1) { boolean ret = true; fieldValue = ""; if (dictFieldTemp.baseType() != Types::Container) { fieldValue = strFmt("%1", _common.getFieldValue(_fieldName, _arrayIndex)); } switch (dictFieldTemp.baseType()) { case Types::String : fieldValue = strReplace(fieldValue, '\n', '\\n'); fieldValue = '\"' + fieldValue + '\"'; break; case Types::Container : fieldValue = '[' + fieldValue + ']'; break; case Types::Enum : dictEnum = new DictEnum(dictFieldTemp.enumId()); fieldValue = dictEnum.name() + '::' + dictEnum.value2Symbol(_common.getFieldValue(_fieldName, _arrayIndex)); break; case Types::Real : fieldValue = num2str(_common.getFieldValue(_fieldName, _arrayIndex), 2, -1, 1, 0); break; case Types::Date : fieldValue = date2str(_common.getFieldValue(_fieldName, _arrayIndex), 123,2,4,2,4,4, DateFlags::None); if (fieldValue == '') { ret = false; } fieldValue = strReplace(fieldValue, '/', '\\'); break; case Types::UtcDateTime : fieldValue = DateTimeUtil::toStr(_common.getFieldValue(_fieldName, _arrayIndex)); if (fieldValue == '') { ret = false; } break; } return ret; } void loopRecord(common _common) { ; for (fieldCounter = dictTable.fieldNext(0); fieldCounter > 0; fieldCounter = dictTable.fieldNext(fieldCounter)) { dictFieldTemp = new DictField(dictTable.id(), fieldCounter); if (dictFieldTemp.isSystem()) continue; if (dictFieldTemp.arraySize() > 1) { arrayCounter = 1; while (arrayCounter <= dictFieldTemp.arraySize()) { dictFieldArray = new DictField(dictTable.id(), dictFieldTemp.id(), arrayCounter); if (setFieldValue(_common, dictFieldArray.name(), arrayCounter)) { source = source + #indent + strFmt("%1.%2 = %3;", dictTable.name(), dictFieldArray.name(DbBackend::Native, arrayCounter), fieldValue) + '\r\n'; } arrayCounter++; } } else { if (setFieldValue(_common, dictFieldTemp.name())) { source = source + #indent + strFmt("%1.%2 = %3;", dictTable.name(), dictFieldTemp.name(), fieldValue) + '\r\n'; } } } source += #indent + strFmt('%1.insert();\r\n', dictTable.name()); } // Modify needed records here dictTable = new SysDictTable(tableNum(CustGroup)); while select CustGroup { loopRecord(CustGroup); } textBuffer = new TextBuffer(); textBuffer.setText(source); textBuffer.toClipboard(); info("@SYS87601"); } |
Tipps und Tricks zum Thema Kompilierung
03.08.2013Microsoft Dynamics AX (Axapta)
Kompilieren gehört zum täglich Brot eines Entwicklers. Leider weist genau dieser Vorgang in Dynamics AX - nennen wir es - Besonderheiten auf. Besonderheit 1: Kein Fortschrittsbalken Wir kennen es alle, man kompiliert eine grössere Menge an Objekten oder vielleicht den ganzen AOT und möchte zumindest ungefähr wissen, wie lange dies noch dauert. Standardmässig gibt’s es kaum eine Möglichkeit dies einzusehen, vor allem wenn der AX-Client seit dem Start der Kompilierung vielleicht schon "eingefroren" ist. In vielen Fällen kann man [CTRL]+[PAUSE] drücken, dadurch wird die Kompilierung kurz angehalten und man kann im Client sehen, bei welchem Objekt man gerade ist. In dem Pause-Dialog sollte man natürlich tunlichst auf [NEIN] klicken, wenn man die Kompilierung danach fortsetzen möchte. Kein echter Fortschrittsbalken, aber zumindest ein Indikator wo man gerade steht. |
|
|
|
|
|
|
Mit Hilfe des Data import/export frameworks ist es möglich, Daten aus externen Systemen in Dynamics AX zu importieren. im folgenden möchte ich dies anhand der Anlage eines Debitoren aus einer CSV-Datei demonstrieren.
Anlegen eines Dateiformates
Anlegen eines Dateiformates unter Data import Export framework > Setup > Source data formats:
Erstellen einer Verarbeitungsgruppe
Erstellen einer Verarbeitungsgruppe (Processing group) unter Data import Export framework > Processing group:
Eintragen der Entitäten
Eintragen der Entities zu dieser Processing group über die Schaltfläche Entities. Im konkreten Fall ist dies die Entität Debitor: