[ DataContractAttribute ,SysOperationContractProcessingAttribute(classStr(SysOperationTemplateUIBuilder)) ] internal final class SysOperationTemplateDC implements SysOperationValidatable { private DimensionValue myDimensionValue; [ DataMember, SysOperationLabelAttribute(literalStr("@SYS105870")), SysOperationDisplayOrderAttribute('3') ] public DimensionValue parmMyDimensionValue(DimensionValue _dimensionValue = myDimensionValue) { myDimensionValue = _dimensionValue; return myDimensionValue; } }
internal final class SysOperationTemplateUIBuilder extends SysOperationAutomaticUIBuilder { private DialogField myDimensionValueField; public void postBuild() { super(); myDimensionValueField= this.bindInfo().getDialogField( this.dataContractObject(), methodStr(SysOperationTemplateDC, parmMyDimensionValue)); if (myDimensionValueField) { myDimensionValueField.lookupButton(FormLookupButton::Always); } } public void postRun() { super(); // register overrides for form control events myDimensionValueField.registerOverrideMethod( methodStr(FormStringControl, lookup), methodStr(SysOperationTemplateUIBuilder, lookupMyDimension), this); } public void lookupMyDimension(FormStringControl _dimensionValueControl) { Name dimensionAttributeName = "CostCenter"; if (_dimensionValueControl != null) { // Construct arguments for the custom lookup Args args = new Args(); args.name(formStr(DimensionLookup)); args.lookupValue(_dimensionValueControl.text()); args.caller(_dimensionValueControl); DimensionAttribute dimensionAttribute = DimensionAttribute::findByLocalizedName( dimensionAttributeName, false, SystemParameters::find().SystemLanguageId); args.lookupField(dimensionAttribute.ValueAttribute); args.record(dimensionAttribute); // Do the lookup FormRun lookupFormRun = classFactory.formRunClass(args); lookupFormRun.init(); _dimensionValueControl.performFormLookup(lookupFormRun); } } }
]]>
OpenPrinter_1: rc:0 LastError:3012(0xbc4) Es wurden keine Drucker gefunden.
]]>
.\SysTestConsole.exe /test:NameOfTestClass1,NameOfTestClass2 /database:axdb]]>
Damit kann man Labelfiles voll automatisiert übersetzen lassen - sehr cool!
Details dazu sind hier zu finden.
]]>Error during AOS start: Cannot create a file when that file already exists.
In meinem Fall hat ein Neustarten der auf Azure gehosteten Umgebung und Neustarten des Schrittes geholfen.
]]>Infolog diagnostic message: 'Cannot create a record in Roles (LogisticsLocationRole). Role: F?hrendes Unternehmen, Head company. The record already exists.' on category 'Error'.
Application configuration sync failed.
Bzw.
Infolog diagnostic message: 'Cannot create a record in Roles (LogisticsLocationRole). Role: Stabile Organisation, Stable organization. The record already exists.' on category 'Error'. 08/20/2019 14:52:12: Application configuration sync failed.
Ursache waren zwei Datensätze in der Tabelle LogisticsLocationRole, die in der Quellumgebung (AX 2012 R2) über den Hotfix KB4048614 erstellt wurden. Dieser Hotfix ist interessanterweise genau jener, den man vor einem Upgrade von AX 2012 R2 auf D365 einspielen soll.
In meinem Szenario konnte ich über das nachstehende Statement die Datensätze löschen, und den Schritt 8 erfolgreich neu starten.
delete from LOGISTICSLOCATIONROLE where type = 104 or type = 105]]>
GlobalUpdate script for service model: AOSService on machine: D365Local
Sync AX database
The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: System.Management.Automation.RuntimeException: An exception of type System.Net.WebException occurred when making an http request to: http://127.0.0.1/ReportServer. Refer to the log file for more details.
The step failed
The step: 25 is in failed state, you can use rerunstep command to debug the step explicitly
at Microsoft.Dynamics.AX.AXUpdateInstallerBase.RunbookExecutor.ExecuteRunbookStepList(RunbookData runbookData, String updatePackageFilePath, Boolean silent, String stepID, ExecuteStepMode executeStepMode, Boolean versionCheck, Parameters parameters)
at Microsoft.Dynamics.AX.AXUpdateInstallerBase.AXUpdateInstallerBase.execute(String runbookID, Boolean silent, String updatePackageFilePath, IRunbookExecutor runbookExecutor, Boolean versionCheck, Parameters param)
at Microsoft.Dynamics.AX.AXUpdateInstaller.Program.InstallUpdate(String[] args)
at Microsoft.Dynamics.AX.AXUpdateInstaller.Program.Main(String[] args)
...kann es möglicherweise helfen, die Reporting Services über den Reporting Services Configuration Manager for SQL Server 2016 manuell zu starten und danach den Updatevorgang beim betroffenen Schritt fortzusetzen.
]]>Das nachstehende Code-Beispiel zeigt. wie man per Code eine Erfassung für eine Kommissionierliste buchen kann (in der Annahme daß beispielsweise alle notwendigen Informationen wie Entnahmelagerplatz usw. bereits eingetragen wurden).
static void pickingListRegistration(Args _args) { WMSPickingRouteID pickingRouteID = "00061"; // Route id to be picked List list = new List(Types::String); list.addEnd(pickingRouteID); WMSPickingRoute::finishMulti(list.pack()); wmsDeliverPickedItems::checkDeliverPickedItems(pickingRouteID, list.pack()); }
]]>
Ich kenne keine Möglichkeit, wie man eine Kommissionierliste nur für bestimmte Auftragspositionen per Code erstellt.
Deshalb nutze ich in dem folgenden Code-Beispiel folgenden Ansatz:
Ich erstelle die Kommissionierliste mit Hilfe des SalesFormLetter-Frameworks und lösche vor dem entscheidenden Schritt (dem Ausführen der run()-Methode) jene Einträge in der Tabelle SalesParmLine, die ich nicht benötigte.
static void createSalesPickingListSingleLine(Args _args) { SalesTable salesTable = salesTable::find("001862"); // Sales order container inventTransIdCon = ["014015", "014016"]; // LOT-IDs to pick SalesFormLetter salesFormLetter; SalesParmLine salesParmLine; salesFormLetter = SalesFormLetter::construct(DocumentStatus::PickingList); // Do the steps manually, which normally are done in method // salesFormLetter.update() salesFormLetter.salesTable(salesTable); salesFormLetter.initParmSalesTable(salesFormLetter.salesTable()); salesFormLetter.transDate(systemDateGet()); salesFormLetter.specQty(SalesUpdate::All); salesFormLetter.proforma(salesFormLetter.salesParmUpdate().Proforma); salesFormLetter.printFormLetter(salesFormLetter.printFormLetter()); salesFormLetter.printCODLabel(NoYes::No); salesFormLetter.printShippingLabel(NoYes::No); salesFormLetter.usePrintManagement(false); salesFormLetter.creditRemaining(salesFormLetter.creditRemaining()); salesFormLetter.createParmUpdateFromParmUpdateRecord( SalesFormletterParmData::initSalesParmUpdateFormletter( salesFormLetter.documentStatus(), salesFormLetter.pack(), true, false, false)); salesFormLetter.initParameters( salesFormLetter.salesParmUpdate(), Printout::Current); salesFormLetter.initLinesQuery(); // Delete unwanted records in SalesParmLine while select forupdate salesParmLine where salesParmLine.ParmId == salesFormLetter.parmId() { if (conFind(inventTransIdCon, salesParmLine.InventTransId) == 0) { salesParmLine.delete(); } } // Let's go salesFormLetter.run(); }
]]>
Das nachstehende Code-Beispiel zeigt. wie man per Code eine Kommissionierliste für alle Positionen eines Verkaufsauftrages erstellen kann.
static void createSalesPickingList(Args _args) { SalesTable salesTable = SalesTable::find("000752"); SalesFormLetter salesFormLetter; salesFormLetter = SalesFormLetter::construct(DocumentStatus::PickingList); salesFormLetter.update(salesTable, systemDateGet(), SalesUpdate::All); }
]]>
public void displayOption(Common _record, FormRowDisplayOption _options) { MyTable myTable = _record as MyTable; super(_record, _options); _options.fontBold(false); if (true) { _options.fontBold(true); } }]]>
Zugriff verweigert: SysOperationController
Ein MenuItem für einen SysOperationController sollte in der Regel wie folgt aussehen:
]]>[SysClientCacheDataMethodAttribute(true)] public display container displayOnHoldImage() { if (trueContition) { return ImageReference::constructForSymbol(ImageReferenceSymbol::Pause).pack(); } return connull(); }
]]>
GlobalUpdate script for service model: RetailSelfService on machine: D365Local
Update Retail self service.
UpdateRetailSelfService.ps1 failed.
The step failed
The step: 52 is in failed state, you can use rerunstep command to debug the step explicitly
at Microsoft.Dynamics.AX.AXUpdateInstallerBase.RunbookExecutor.ExecuteRunbookStepList(RunbookData runbookData, String updatePackageFilePath, Boolean silent, String stepID, ExecuteStepMode executeStepMode, Boolean versionCheck, Parameters parameters)
at Microsoft.Dynamics.AX.AXUpdateInstaller.Program.InstallUpdate(String[] args)
at Microsoft.Dynamics.AX.AXUpdateInstaller.Program.Main(String[] args)
...kann es möglicherweise helfen, den Azure Storage Emulator zu starten und danach den Updatevorgang beim betroffenen Schritt fortzusetzen.
]]>System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch(); watch.Start(); // ... do something watch.Stop(); info(strFmt("%1ms", watch.ElapsedMilliseconds));]]>
Das nachstehende Code-Beispiel zeigt, wie man per Code eine Kommissionieriste erfassen kann und dabei nur Teilmengen verarbeitet.
Dabei verfolge ich den Ansatz, für die benötigte Menge eine eigene Zeile in der Tabelle WMSOrderTrans zu erstellen (über die Teilen-Funktion) und die aktuell nicht benötigte Menge zu stornieren.
static void pickingListRegistrationPartly(Args _args) { WMSPickingRouteID pickingRouteID = "00066"; // Route id to be picked Map inventTransMap = new Map(Types::String, Types::Real); MapEnumerator me; InventTransId inventTransId; Qty pickQty; List list = new List(Types::String); WmsOrderTrans wmsOrderTrans; WmsOrderTrans wmsOrderTransNew; list.addEnd(pickingRouteID); // Build map containing the lot-ids and quantity to pick inventTransMap.insert("014023", 7); inventTransMap.insert("014026", 3); // Change quantity me = inventTransMap.getEnumerator(); while (me.moveNext()) { inventTransId = me.currentKey(); pickQty = me.currentValue(); ttsBegin; select forupdate wmsOrderTrans where wmsOrderTrans.RouteId == pickingRouteID && wmsOrderTrans.inventTransId == inventTransId && wmsOrderTrans.FullPallet == NoYes::No && (wmsOrderTrans.ExpeditionStatus == WMSExpeditionStatus::Registered || wmsOrderTrans.ExpeditionStatus == WMSExpeditionStatus::Activated || wmsOrderTrans.ExpeditionStatus == WMSExpeditionStatus::Started); // Split line wmsOrderTransNew = wmsOrderTrans.split(pickQty); ttsCommit; ttsBegin; // Cancel remaining line wmsOrderTrans.cancel(); ttsCommit; } // Update WMSPickingRoute::finishMulti(list.pack()); wmsDeliverPickedItems::checkDeliverPickedItems(pickingRouteID, list.pack()); }]]>
[BPFrameworkFatalException]:A fatal exception occured in the Best Practices framework.
In all diesen Fällen hat es geholfen, die folgende Regel zu deaktivieren: BPCheckTableDimensionFields
Gefunden habe ich den entscheidenden Tip in der Dynamics 365 Community
]]>DimensionDisplayValue displayValue = LedgerDimensionFacade::getDisplayValueForLedgerDimension(ledgerJournalTrans.LedgerDimension);]]>
public static void setFormViewOption( FormRun _formRun, FormViewOption _formViewOption = FormViewOption::Details ) { if (_formRun && _formRun.viewOptionHelper()) { _formRun.viewOptionHelper().setViewOption(_formViewOption); } }
]]>
Voreinigen Jahren hatte ich schon mal einen Beitrag über die Klasse EditorScripts geschrieben. Das ist nämlich jene, mit deren Hilfe man die Funktionalität des X++-Editors recht einfach erweitern kann.
Beispielsweise kann man das Kontextmenü des Editors einfach erweitern, in dem man in der Klasse Methoden erstellt.
Diese Methoden müssen lediglich einige Kriterien erfüllen, das sind u.a.:
Welche weitere Kriterien sie erfüllen muss, kann man in der Methode EditorScripts.isApplicableMethod() nachlesen.
Sind diese Kriterien erfüllt, kann man über die Benennung der Methode sogar die Menüstruktur steuern, dabei wird der Unterstrich "_" als Trennzeichen genutzt.
Die folgende Methode würde beispielsweise ein Untermenü MyExtensions mit einem Eintrag Example erzeugen.
//AOSRunmode::Client public void MyExtensions_Example(Editor _editor) { //...do something... }
Auch verschachtelte Untermenüs sind so möglich:
//AOSRunmode::Client public void MyExtensions_Examples_Example1(Editor _editor) { //...do something... }
Weiters wird durch die Benennung der Methode indirekt ein Schlüsselwort definiert, welches im Editor ähnlich wie FOR, WHILE, SWITCH,… in Kombination mit der Tabulatortaste verwendet werden können.
Das Schlüsselwort ist jenes, nach dem letzten _ des Methodennamen.
Im folgenden Beispiel wäre "devVersion" (Gross-/Kleinschreibung beachten) das Schlüsselwort.
public void template_method_devVersion(Editor _editor) { // do something }
Über die oben genannte Methode EditorScripts.isApplicableMethod() kann man übrigens auch steuern, wann eine Methode angeboten werden soll.
]]>Ein solcher Fall sind beispielsweise Eventhandler, die ein vorgegebenes Paramterprofil aufweisen, im Falle eine Nicht-Verwendung eines dieser Parameter aber eine BP-Abweichung verursachen.
class MyFreeTextInvoiceHeaderFooterTmpEH { [DataEventHandler(tableStr(FreeTextInvoiceHeaderFooterTmp), DataEventType::Inserting)] public static void FreeTextInvoiceHeaderFooterTmp_onInserting(Common sender, DataEventArgs e) { FreeTextInvoiceHeaderFooterTmp freeTextInvoiceHeaderFooterTmp; freeTextInvoiceHeaderFooterTmp = sender; if (freeTextInvoiceHeaderFooterTmp.CompanyBankAccount == "") { freeTextInvoiceHeaderFooterTmp.CompanyBankName = "Unknown"; } } }
Bei obigem EH würde folgende BP-Abweichung ausgegeben werden, da der Parameter e nicht verwendet wird:
BP Rule: [BPParameterNotUsed]:The parameter 'e' is not used.
Um dies zu verhindern kann man im einfachsten Fall das Attribute SuppressBPWarning verwenden:
class MyFreeTextInvoiceHeaderFooterTmpEH { [DataEventHandler(tableStr(FreeTextInvoiceHeaderFooterTmp), DataEventType::Inserting), SuppressBPWarning('BPParameterNotUsed', 'False positive')] public static void FreeTextInvoiceHeaderFooterTmp_onInserting(Common sender, DataEventArgs e) { FreeTextInvoiceHeaderFooterTmp freeTextInvoiceHeaderFooterTmp; freeTextInvoiceHeaderFooterTmp = sender; if (freeTextInvoiceHeaderFooterTmp.CompanyBankAccount == "") { freeTextInvoiceHeaderFooterTmp.CompanyBankName = "Unknown"; } } }
Es besteht aber auch die Möglichkeit, all diese Suppressions in einer modulgebundenen Datei zu sammeln. Dazu muss man im Verzeichnis des Modules eine XML-Datei wie folgt anlegen:
K:\AosService\PackagesLocalDirectory\YourModel\YourModel\
AxIgnoreDiagnosticList\YourModel_BPSuppressions.xml
<?xml version="1.0" encoding="utf-8"?> <IgnoreDiagnostics> <Name>YourModel_BPSuppressions</Name> <Items> <Diagnostic> <DiagnosticType>BestPractices</DiagnosticType> <Severity>Warning</Severity> <Path>dynamics://Class/MyFreeTextInvoiceHeaderFooterTmpEH/Method/FreeTextInvoiceHeaderFooterTmp_onInserting</Path> <Moniker>BPParameterNotUsed</Moniker> <Justification>Event handler parameters that cannot be removed from the method's signature.</Justification> </Diagnostic> </Items> </IgnoreDiagnostics>
Darauf achten, daß im XML der Name des Models den Gegebenheiten angepasst werden muss.
DiagnosticType ist immer "BestPractices". Severity, Path und Moniker können meist aus der Fehlermeldung in der Error list von Visual Studio ausgelesen werden. Meiner Erfahrung nach aber leider nicht immer. In diesem Fall kann man auf dieser Seite nachlesen, ob man einen passenden Moniker findet:
Mir selbst sind bislang die folgenden Monikers über den Weg gelaufen:
Sobald man eine solche XML-Datei angelegt hat, kann man das Kontextmenü des Projektes von Visual-Studio nutzen, um die Datei zu berarbeiten: Edit Best practice suppressions
Man kann für diese XML-Datei übrigens im selben Verzeichnis ein XML-Schema ablegen, um den folgenden Fehler zu vermeiden:
Could not find schema information for the element 'IgnoreDiagnostics'.
Ein solches Schema erstellt man über Visual-Studio unter XML > Create schema (dieses Menü ist nur verfügbar, wenn man grade eine XML-Datei geöffnet hat).
Um die XML-Datei (und ggf. das Schema) zur Versionskontrolle hinzuzufügen, muss man über den Source Control Explorer in das Verzeichnis des entsprechenden Models wechseln und über Add Items to Folder die Dateien auswählen.
Übrigens hab ich die Erfahrung gemacht, daß man die XML-Datei ausserhalb von Visual-Studio bearbeiten sollte.
]]>GlobalUpdate script for service model: AOSService on machine: D365Local
Sync AX database
The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: System.Management.Automation.RuntimeException: An exception of type System.Net.WebException occurred when making an http request to: http://127.0.0.1/ReportServer. Refer to the log file for more details.
The step failed
Die Ursache für diesen Fehler war rasch gefunden - nach dem Download des VPC aus LCS wurde der Name des virtuellen Computers geändert, dieser aber nicht überall nachgezogen. Dadurch hatten die SQL Reporting Services ein Problem, hier musste eine neue Database eingetragen werden. Wie dies geht, und was man noch alles beim Umbenennen eines VPCs berücksichtigen muss steht übrigens hier.
]]>Executing step: 19
GlobalUpdate script for service model: AOSService on machine: D365FOSHLocal
Sync AX database
The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: System.Management.Automation.RuntimeException: An exception of type System.Net.WebException occurred when making an http request to: http://127.0.0.1/ReportServer. Refer to the log file for more details.
The step failed
The step: 19 is in failed state, you can use rerunstep command to debug the step explicitly
at Microsoft.Dynamics.AX.AXUpdateInstallerBase.RunbookExecutor.ExecuteRunbookStepList(RunbookData runbookData, String updatePackageFilePath, Boolean silent, String stepID, ExecuteStepMode executeStepMode, Boolean versionCheck, Parameters parameters)
at Microsoft.Dynamics.AX.AXUpdateInstallerBase.AXUpdateInstallerBase.execute(String runbookID, Boolean silent, String updatePackageFilePath, IRunbookExecutor runbookExecutor, Boolean versionCheck, Parameters param)
at Microsoft.Dynamics.AX.AXUpdateInstaller.Program.InstallUpdate(String[] args)
at Microsoft.Dynamics.AX.AXUpdateInstaller.Program.Main(String[] args)
...kann die Ursache sein, daß der Computername des VPCs geändert wurde, und dabei aber nicht alle von Microsoft empfohlenen Schritte durchgeführt wurden.
Welche das sind, ist hier zu finden: https://docs.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/migration-upgrade/vso-machine-renaming
]]>