Code highlighting

Showing posts with label Microsoft Dynamics. Show all posts
Showing posts with label Microsoft Dynamics. Show all posts

Tuesday, July 14, 2020

Flights vs Feature management - practical overview

Terminology

The primary purpose of flights and features both is to implement a controlled roll-out of new functionality into the product, so as not to disrupt existing business operations. One of Microsoft's primary goals with OneVersion is to ensure we do not break anyone.

By their nature, both the flights and the features are transient in nature, i.e., they will be removed from the product after some time, leaving the new functional behavior as default going forward.

Let's now break it down a bit more, looking closer at what a flight is, and what a feature is, and who has control over them, and when they are enabled/disabled.
  • A flight is a switch, which controls a certain, usually small, piece of business logic in the product, leading it one way or the other. Microsoft is in control of the state of the flight for customers in PROD. The decision to enable or disable a flight, as long as that does not impact the functionality of the environment, is not initiated by the company in most cases. Flights are, generally speaking, always OFF by default.
  • A toggle is a business logic / X++ construct, that is typically based on flights. Similar to flights, it is either On or Off. So what's interesting here is what the default out of the box state of the toggle is.
    • An "enabled by default" toggle, or, as it's more commonly known, a Kill Switch, is used for smaller changes, and the product behavior is changed right away, with no additional action from the users. Kill switches is a way for Microsoft to introduce bug fixes, yet safeguard against potential functional regressions, and as such, ensure we do not break anyone. If controlled by a flight, enabling the flight would turn the toggle and the new behavior off. Example: WHSShipmentConsolidationLoadLineLoadIdToggle
      • A kill switch will get removed after around a year from the moment of introduction, making the new behavior apply to everyone always from then on. 
    • A "disabled by default" toggle is used much less frequently
      • To enable certain troubleshooting logic that is performance intensive and thus undesirable in PROD. Example: WHSInventOnHandForLevelCalculatorMissingSiteIdLogToggle
      • To hide incomplete or work-in-progress features, so they cannot be enabled in PROD. Examle: WHSDockInventoryManagementFeature
      • Enabling the flight would mean enabling the toggle
  • A feature is, in a similar way, a toggle, which controls a usually larger piece of business logic, and is typically controlled by the superusers in the company. For this, they use the Feature management dashboard. Example: WHSWaveLabelPrintingFeature
    • Features go through a life cycle, where they are
      • In Development
        • Not available for use by end users in PROD
      • Private preview
        • Available for use by end users, including in PROD, but only based on prior agreement with Microsoft
        • Linked to a flight, which Microsoft needs to enable for the feature to appear
      • Public preview
        • Available for use by end users in PROD, based on decision to enable and configure the feature by superusers.
      • Generally available / Released
        • This is now the default behavior of the system, everyone has it. Depending on the feature, can still be configured "off".
    • There are corresponding kill switches in place to disable new feature behavior if a severe issue is discovered with the feature. Only reserved for very rare cases.
    • You can read more about Feature management here.

In an ideal world, partners/superusers should not need to know/worry about flights, and should control the behavior of the system solely through the Feature management dashboard. 

All of the above applies to PROD environments. The story with DEV/UAT environments is a bit different today for flights, where the partner/superuser has much more control. 

Note. Self-Service Deployment environments are controlled a bit differently, where above is also true for UAT type environments.

DEV / UAT environments

You can control the state of flights through the SysFlighting table. This is sometimes referred to as "static flighting". 
It's nicely described in an old post I found here, so I'll just paste the one line SQL statement you can use here instead:

INSERT INTO dbo.SYSFLIGHTING(FLIGHTNAME, ENABLED, FLIGHTSERVICEID) VALUES ('', 1, 12719367)

It's really as simple as that, but remember to evaluate, if the toggle is enabled by default or disabled by default before you start adding flights there. Maybe the logic is already doing what you want.

Tip For Inventory, Warehouse and Transportation toggles it is very easy, as you can see it from the name of the class the toggle extends from:

  • WHSEnabledByDefaultToggle
  • WHSDisabledByDefaultToggle

FAQ / Typical scenarios

I enabled the flight in UAT/GOLD by inserting into SysFlighting, but when going live, the flight in PROD is not enabled. Microsoft, please insert the record for me.

Flights in PROD are controlled in a completely different way, not through the SysFlighting table, and are also not enabled on customer's request, but only when it makes sense after evaluation by Microsoft.

Important. You should not expect that just because you turned on a flight in UAT/DEV, and liked what you saw, you'll get the same in PROD. Microsoft can reject your request for enabling particular flighted (private preview) behavior in your PROD environment.

If you discovered what you believe to be a functional or performance regression, and you managed to link it to a particular kill switch, please log a support request with detailed repro steps, so Microsoft can evaluate and fix. We will then typically enable the corresponding flight to mitigate short term.

I'm trying to disable a flight but it's not working. I inserted a row in SysFlighting and marked it as Enabled=0, but system still behaves in the same way as before

Flights are, generally speaking, only Enabled. So it just really depends on what kind of toggle this is in X++. If this is a "kill switch", enabling the flight will revert the behavior to how it was before the changes. If this is a private preview feature, enabling the flight will enable the new behavior (or simply show the feature in the Feature management dashboard)

This is easy to understand if you look at the actual implementation in the WHSEnabledByDefaultToggle class.
public boolean isEnabled()
{
    ...         
    if (flightName == carbonFlightName)
    {
        return !isFlightEnabled(flightName);
    }
    ...
}

This is basically reversing the condition for the flight. Meaning, enabling the flight will disable the toggle.

I enabled a feature, but I did not mean to, and now I cannot disable it

Some features are indeed designed in a way, where it is not allowed to disable them. The common reason here is that as part of enabling this feature, or as a result of using the feature for a short time, data was modified, which makes it difficult to revert to the old behavior. 

Also, not all features have a separate dedicated configuration switch in the product, so after you enable the feature, the product will in most cases start behaving in a different way.

So please carefully read the description of the feature, as well as evaluate the consequences of enabling it in UAT, before enabling it in PROD.

Microsoft enabled a private preview feature flight for me, but I still cannot see the feature in Feature management dashboard

Make sure you have clicked on "Check for updates" in the dashboard after 10-15 minutes after the flight was enabled.

Tip Microsoft also has a way of checking if the flight / feature is evaluated as On or Off for a specific environment, which can help you troubleshoot any issues, if it comes to that.

What is the difference between a Feature and regular Module parameters? For example, a Feature in HR - "Filter active positions" (This feature enables a position list filtered to only active positions). Why it is not an HR module parameter?

A feature is meant for controlling the roll-out of functionality, while a configuration parameter is meant for allowing the user flexibility in setting up their system. 

In the specific example above the intention with the "Filter active positions" feature is to expose it to all users (it's available in Feature management dashboard for everyone to see and enable), but in a manner which would not introduce new menu items / forms until this explicit decision is taken by the superuser, so as to ensure any documentation and internal task guides are properly updated beforehand. It is NOT the intention to make this behavior configurable going forward, and all companies will have access to the relevant menu item eventually (note, how the new FeatureClass property is set on it accordingly), while the feature class will be removed from the product. This approach also allows Microsoft to simplify the product, avoiding addition of unnecessary parameters.

That is not to say that both cannot co-exist. A module parameter or even whole new configuration screens can be added for proper setup of a new feature. They will only be exposed to the end users once the feature is enabled in Feature management. But then for the feature to function according to business expectations one or more settings need to be configured. An example of this is WHSShipConsolidationPolicyFeature feature, which you need to configure after enabling by setting up Shipment consolidation policies in the corresponding form.

More questions?

Please leave comments below, and I'll try to clarify!

Thursday, August 10, 2017

Announcement: Plan of record design for InventDim extensibility

As you all hopefully know by now, we are on a journey towards overlayering-free solutions.
One big roadblock on this path was the Inventory dimension concept we have in AX, or, more specifically, providing a way for partners to add new inventory dimensions that would not require overlayering.

Michael has documented the current design we have in mind in a blog post you can read below:
https://blogs.msdn.microsoft.com/mfp/2017/08/10/extensible-inventory-dimensions/

Let me know if you have any questions!

Thursday, March 02, 2017

Extensible enums: Breaking change for .NET libraries that you need to be aware of

A while back I wrote a blog post describing the Extensible Enums - a new feature that is part of Dynamics 365 for Operations:
http://kashperuk.blogspot.com/2016/09/development-tutorial-extensible-base.html

I explained that when marking an enumeration as extensible, the representation of this enum under the hood (in CLR) changes. Here's the specific quote:
The extensible enums are represented in CLR as both an Enum and a Class, where each enum value is represented as a static readonlyfield. So accessing a specific value from the above enum, say, NumberSeqModule::Invent would under the hood look something like NumberSeqModule_Values.Invent, where Invent is of type NumberSeqModule which is an Enum. It would in turn call into AX to convert the specific named constant "Invent" to its integer enumeration value through a built-in function like the symbol2Value on DictEnum.
Something that was not super clear in the post is that this was actually a breaking change that might impact your .NET solutions relying on one of these base enumerations.

Problem statement

As part of enabling further extensibility in the application for the next release, we have made a number of additional base enums extensible.

Let's take enum TMSFeeType as an example. Assume we have made it extensible in X++. That means that in our C# project, where we use this enum, we will no longer be able to access it from Dynamics.AX.Application namespace by name, like so:

switch (accessorialFeeType)
{
    case TMSFeeType.Flat:
        // Do something
        break;

    case TMSFeeType.PerUOM:
        // Do something else
        break;
    // etc.
}

If you navigate to its definition, you will notice that the enum declaration is empty:

namespace Dynamics.AX.Application
{
    public enum TMSFeeType
    {
    }
}

The proper way to use the enum that is extensible is to reference the above mentioned class suffixed with _Values, which lives in the Dynamics.AX.Application.ExtensibleEnumValues namespace, like so:

if (accessorialFeeType == TMSFeeType_Values.Flat)
{
    // Do something
}
else if (accessorialFeeType == TMSFeeType_Values.PerUOM)
{
    // Do something else
}

Note: Because the values are now determined at runtime by going to the AOS and asking for the correct integer value of this enum, they cannot be used in a switch/case block, which expects constant expressions known at compile-time.

What's next

Obviously, this situation is not great. 
Let's hope that Microsoft will think of a good way to address this going forward.

Question to you

That leads to a question - how many of you actually have .NET libraries relying on application code in Dynamics 365 for Operations and might be impacted by us making some of the enums extensible in the next major release?

Friday, November 04, 2016

Tutorial Link: Executing outbound work with pending demand replenishment work

Introduction

In Dynamics 365 for Operations we solved one of the long-standing complaints, where large work orders could not be started because of pending replenishment. A typical workaround then would be to artificially broke down the replenishment lines into a separate work order, so workers can do the picking for the majority of stuff. Then of course you'd get into problems with merging the two (or more) Target LPs onto one (which we now also support - see my previous blog post).

Read the feature description and and walk through a sample flow on our SCM blog:
https://blogs.msdn.microsoft.com/dynamicsaxscm/2016/11/04/processing-work-that-is-awaiting-demand-replenishment/

For those on AX 2012 R3

We have not back-ported this feature to AX 2012 R3 yet. We have it in the backlog, but no ETA for when that will happen.

Update: This is now available on LCS under KB3205828

Feedback

We'd love to hear your feedback on this feature if you are going to use it in your production environments.



Wednesday, November 02, 2016

Tutorial: Movement of inventory with associated work in Warehouse management, Dynamics 365 for Operations (1611)

Introduction to supported scenarios

For the Fall release of Dynamics 365 for Operations (1611) we have built various features to support the theme of increasing the flexibility in the daily operations of warehouse workers.

Imagine the following scenarios:

Scenario 1

A company has a relatively small receiving area, and it’s congested with pallets and boxes awaiting put away. A large shipment is expected on this day, so the receiving clerk decides to clear up the receiving area, moving some of the pallets to a secondary inbound staging area.

Scenario 2

An experienced warehouse worker going around the warehouse notices an opportunity to consolidate items in one location instead of having them spread out across 3 nearby locations with a little quantity in each. He wants to move items from each of these locations into the same location onto the same license plate, consolidating the quantity.

Scenario 3

A pallet is awaiting shipment in a staging location, say, STAGE01, which is near BAYDOOR01. However due to a change of plans the truck is going to arrive to BAYDOOR04. The shipping clerk is aware of this and needs to ensure the truck does not have to hang around waiting to be loaded from STAGE01. The shipping clerk therefore decides to move the items in that shipment from STAGE01 to STAGE04, much closer to their new destination.


All of these scenarios are not possible today due to one simple fact – the items that need to be moved have work pending for them, meaning they are physically reserved on the warehouse location level (or even the license plate level) and therefore cannot be moved.

We have built this capability in for the Fall release of Microsoft Dynamics 365 for Operations (1611). Now you can decide, which warehouse workers are allowed to move reserved inventory, and which are not. This will allow some regulated warehouses the flexibility for cases where they may not accept that a worker can decide upon a new pick location from an already created pick work, or that a warehouse manager would like to steer which capabilities his un-experienced worker should have.

Scenario 2 walkthrough

In the standard demo data in company USMF we already have some data that can help showcase this new scenario on warehouse 24.
There are two sales orders, order 000748 and order 000752, both of which are planning to ship 10 pcs of A0001, and both have been released to warehouse, so corresponding work orders USMF-000001 and USMF-000002 exist, both to pick 10 pcs of A0001 from location FL-001. There is a total of 100 pcs of A0001 in this location, but only 80 is physically available because of the two work order reservations.
So if warehouse worker 24 tried to open the Movement mobile device menu item on his mobile device, he would see the following picture for location FL-001:

Moving physically available quantity

As you can see, the worker is only allowed to move 80 of the 100 pcs physically present in the location. Let’s fix that, and configure the worker to allow him moving reserved inventory.

Configure worker to allow movement of inventory with associated work

Now, if we go into the movement flow on the mobile device again, the screen will look as below:

Moving all physical inventory from a location

Now that the worker is allowed to move reserved inventory, he can move all 100 pcs of item A0001. Let’s go ahead and do that, moving the items to FL-007 to a new license plate LP_V_001.

Movement of inventory - To information

Let’s now review what happened behind the scenes:
  1. A new Inventory Movement work was created, from FL-001 to FL-007, for 100 pcs of A0001. It was immediately executed, and there the Work status is Closed.
  2. All related work orders will be updated, so they point the Pick line to location FL-007 instead of location FL-001, as you can see on the screenshot below.
Work order after inventory was moved to FL-007

Now location FL-001 is empty and can be used according to what warehouse worker 24 had in mind, for example, to put away the goods just reported as finished (and FL-007 was smaller in size and did not fit the RAFed pallet).

The other two scenarios are pretty much the same in terms of the flow, with the only difference being the reservations behind.

Current limitations

  • The work reservations that are possible to move as of today are limited to Sales order, Transfer order issue, Transfer order receipt, Purchase order and Replenishment.
  • Moving the items is restricted in a way that prevents splitting of work lines. So if you have a work line for 100 pcs of item A from location Loc1, you won’t be able to move only, say, 30 pcs of item A from there to another location, as that would lead to split of the existing work line to 30 and 70, as the locations are now different.
  • For Staging scenarios, where the license plate we move the goods from, or the license plate we move the goods to, are set as a Target LP for a work order, only movement of the entire LP is allowed, so as not to break up the Target LP.
  • Only the ad hoc movement is currently supported. That means you will not be able to move reserved inventory through the movement by template mobile device menu items.

For those behind on updates :)

This feature has also been back-ported to Microsoft Dynamics AX 2012 R3 and will be available as part of CU12.
It can also be downloaded individually through KB number 3192548



This is great stuff, give it a try and let us know if you have any feedback!

Thanks


Announcement: Microsoft Dynamics 365 – Now generally available

Including Microsoft Dynamics 365 for Operations formerly known as Microsoft Dynamics AX aka Axapta

The Fall release is here, meaning all sorts of goodies both in platform and application are now available for you to try out (You can actually do it for free for a week before you make a decision to buy).

You can read the announcement by our CVP on the Microsoft Community blog

To learn more about Dynamics 365 visit https://www.microsoft.com/en-us/dynamics365/home

To learn about all the new or changed features, both in platform and application, visit our wiki page:
https://ax.help.dynamics.com/en/wiki/whats-new-or-changed-in-dynamics-ax-7/


Let me know what you think of it!

Saturday, October 08, 2016

Tutorial: Visual Studio Debugger capabilities in Microsoft Dynamics AX '7', or the case-sensitive horror of C# syntax

Introduction

With the move to Visual Studio with the release of Microsoft Dynamics AX 7, the debugging experience also moved to use the standard Visual Studio debugger.
That means that we get some goodies that were previously not available in the MorphX debugger.

One example of that is the Immediate Window, which allows you to write expressions that are evaluated in the context of the currently hit breakpoint in X++ code.
This basically gives you the ability to call methods, look up variable values, ultimately allowing to change the current state. That is obviously a very useful feature.
Unfortunately, the current version does not fully support X++, meaning there are certain quirks when it comes to using it.

In this post I will describe the capabilities and syntax you need to use, so you can overcome some of the learning curve that comes with the new debugger.

Restrictions

Here's a list of the quirks you'll have to account for:

  • There is no native support for X++, so you need to use C# syntax.
  • Intellisense for X++ is not provided. This is a consequence of the way the expression evaluator works.
  • X++ is case insensitive, while C# is not. This means that references made to identifiers need to be in the case that was used at the place of definition.
  • Not all expressions allowed in X++ are applicable. One unfortunate example is select statements. You can however use static find() methods if they exist
  • Since it's not X++, you cannot use X++ types, like str, boolean, utcdatetime. Instead, use the C# equivalents. EDTs are not preserved during compilation either, so, again, use base types. Base enums is the only exception, but, again, you need to use C# syntax
  • When invoking methods from class Global, you will need to use the full notation, Global.methodName()
  • Single quotes are used to represent characters in C#, so you should only use double quotes for representing strings
  • The Expression evaluator has no knowledge of labels, so you will need to use workarounds, like SysLabel.labelId2String("@WHS1399"), if necessary.
  • Intrinsic functions like fieldNum() are not available - you'll need to use a workaround, as I will show below, using Microsoft.Dynamics.Ax.Xpp.PredefinedFunctions
  • You may end in a situation where the types you want to use are not loaded. You can use the ReflectionCallHelper to load these types – As soon as they are loaded you will be able to use them normally. Use the following command in the immediate window to load a particular type: Microsoft.Dynamics.Ax.Xpp.ReflectionCallHelper.getType("Global")
Now, with that out of the way, let's look at some examples.

Examples

Immediate Window capabilities in Microsoft Visual Studio for Dynamics AX 7
Let's walk through these examples line my line, and I'll explain what happened in each case:
  1. worKLine - as you can see, it's not a problem for the compiler, because X++ is not case sensitive, but it is a problem for the debugger, which is. So worKLine with capital K will not be recognized, while workLine will be treated just fine. This is the reason for one of the most confusing moments with the new debugger - hovering over the worKLine variable in the code editor will not show its value, even though everything looks fine and compiles. 
  2. Even more evident is the following example, where workLine.wMSLocationId value cannot be shown when hovering over it. Nor can it be recognized as an existing field in the Immediate Window. That's because it was defined as WMSLocationId on the table. Again, casing is very important in the new debugger, so pay attention when you write code
  3. Finally, success, we use the right record variable name and the right field name - so we got our result, the value of that field in the current record.
  4. We are trying to invoke a method which resides on the Global class exactly the same way it is done in the code we are debugging, but that won't work, the method is not recognized.
  5. Now we try to invoke it using the full notation, Global::exceptionTextFallThrough(); - That does not work either, because we must use C# syntax, and :: is only X++
  6. Finally, we use the right notation, invoking Global.exceptionTextFallThrough() - that works. The method does nothing and returns no result, and we are informed about that
  7. Trying to get the value of a Base Enum using X++ notation will not work
  8. Using the "correct" C# notation will return the right result, WHSWorkStatus.Open
  9. Microsoft.Dynamics.Ax.Xpp contains a number of helpful classes to compensate for lack of full X++ support. TrueFalseHelper is one of them, and its method TrueFalse() will use the X++ logic for evaluating if an expression is true or false. We use it here and pass in the record buffer. It returns true, because the record has been selected. In real C# that would fail, as the record cannot be implicitly converted to bool, along with most other X++ types, like str, integer, etc. 
    1. Another example from this namespace is EqualHelper.Equal() which can compare two X++ types
  10. Yet another example is the PredefinedFunctions class. You can see all the available methods in the Appendix. Here we invoke the tableName2Id(), passing in the string containing WHSWorkLine. Remember 'single quotes' do not work, only "double quotes". In this case all looks good, but the function is not recognized. That's again because of the casing. This class is very inconsistent about the casing of its methods - so you just have to remember the ones you commonly use, or use the robust "trial-and-error" approach.
  11. Finally, using the right casing we get the expected result, the ID of WHSWorkLine table

I have on purpose taken the full screenshot, so you could see some of the other windows open in Visual Studio while debugging:
  • Locals window, which is similar to the Watch window, but shows the values for all local variables without you first adding them to the list. 
  • The Infolog window will show all the infolog messages, which is very convenient
  • The Callstack is pretty much the same as in X++, with the downside of showing the full types, meaning you see a lot of useless type namespace information which X++ developers are not used to
  • The Breakpoints window shows all of your breakpoints, and you can for each one decide to configure it further, disable it or remove it. You can now make the breakpoints conditional, however since it uses the same Expression evaluator, I had trouble with it, so I stopped using it after a while. The counter condition works fine though, so you can use that in various complex loops and stuff, setting the breakpoint inside the loop.
  • Autos window, which is supposed to show the current line variables plus any from the previous line is useful, because it shows the global state variables on top of that, ttslevel in particular. Company, Partition and UserId are of lower interest.
  • Watch window - that's as expected, you add variables, their values are shown and can be edited on the fly. Note all the above restrictions apply here as well, so watch the casing and syntax.

Conclusion

As you can see, the Visual Studio debugger is much more powerful than what we had in AX 2012 and prior, however it also has a number of limitations due to lack of support for X++ language. Note, that it's not just X++, other languages which you can use in VS also have problems here and there.

Let me know how you find the new debugger. What features do you like? Something you miss from the old days?

Appendix

This appendix lists the predefined functions in the Microsoft.Dynamics.Ax.Xpp.PredefinedFunctions class. Pay special attention to the casing for the below methods.

Note. The methods starting with q deal with containers.
  • decimal Abs(decimal arg);
  • decimal AcceleratedDepreciation(decimal price, decimal scrap, decimal life, int period);
  • decimal Acos(decimal arg);
  • void AddToContainer(object element, int index, object[] container);
  • object Any2Enum(object a);
  • Guid any2guid(object input);
  • Date Anytodate(object arg);
  • decimal Asin(decimal arg);
  • object[] AssignPlusToContainer(object element, object[] container);
  • decimal Atan(decimal arg);
  • void Beep();
  • void catchUCDK(int ttsCount);
  • IDisposable changecompany(string newCompany);
  • int Char2Num(string text, int position);
  • int classget(object value, int classIdByType);
  • string ClassId2Name(int classId);
  • int classidget(XppObjectBase obj, int objId);
  • int ClassName2Id(string className);
  • int CompareStrings(string l, string r);
  • int ConfigurationKeyNum(string configurationKey);
  • object ContainerPack(object element);
  • object ContainerUnpack(object element);
  • decimal ContributionRatio(decimal sale, decimal purchase);
  • decimal corrflagset(decimal real, int arg);
  • decimal Cos(decimal arg);
  • decimal Cosh(decimal arg);
  • string curext();
  • string curusrid();
  • int Date2Num(Date date);
  • string Date2Str(Date date, int sequence, int day, int separator1, int month, int separator2, int year);
  • string Date2StrConvert(Date date, int sequence, int day, int separator1, int month, int separator2, int year, int convert_to_calendar);
  • string Datetime2Str(utcdatetime d, int f);
  • string DayName(int number);
  • int Dayofmth(Date d);
  • int DayOfWeek(Date arg);
  • int Dayofyr(Date d);
  • decimal Decround(decimal figure, int decimals);
  • object DefaultValue(Types t);
  • string dellspc(string text);
  • IntPtr delprefix(IntPtr value);
  • string delrspc(string text);
  • string delstr(string text, int position, int number);
  • decimal Depreciation(decimal price, decimal scrap, decimal life, int period);
  • int Dimof(object o);
  • Date EndMonth(Date arg);
  • string Enum2Str(object e);
  • string EnumExtension2Str(object value, string enumTypeName);
  • int Enumname2id(string enumName);
  • int EnumSymbol2EnumValue(string enumName, string enumValueName);
  • string EnumTypeToString(Types type);
  • decimal Exp(decimal arg);
  • decimal Exp10(decimal arg);
  • string Fieldid2name(int tableId, int field, int arrayIndex);
  • string Fieldid2pname(int tableId, int field, int arrayIndex);
  • int Fieldname2id(int tableId, string fieldName);
  • void FillArray(int size, T value, Dictionary array, T zeroValue);
  • void FillEdtArray(int size, T value, EdtArray array);
  • string FldPNam(int dataset, int fieldnum);
  • void Flush(int dataset);
  • decimal formattedstr2num(string text);
  • decimal Frac(decimal arg);
  • decimal FutureValue(decimal Payment, decimal Interest, decimal Life);
  • string getbuildversion();
  • string getcurrentauthor();
  • string getcurrentbranchname();
  • string getcurrentcustomerid([Optional, DefaultParameterValue(0)] int dbFlag);
  • string getcurrentdevicename();
  • string getcurrentipaddress();
  • string getcurrentmachinename();
  • long getcurrentpartitionrecid();
  • Guid getcurrentrequestid();
  • string getcurrentruntimemessage();
  • string getcurrentserviceunitid();
  • string getcurrentserviceunittype();
  • string getcurrentsessionid();
  • string getcurrenttenant();
  • string getcurrentuserid();
  • string getcurrentuserlanguage();
  • Dictionary GetFieldQCollection();
  • T GetFromArray(int position, Dictionary array, T zeroValue);
  • Date GetNullDate();
  • utcdatetime GetNullDateTime();
  • string GetNullString();
  • string getprefix();
  • void GroupQ(Dictionary collection);
  • string Guid2Str(Guid value);
  • decimal Idg(decimal purchase, decimal contribution_ratio);
  • string image(object o);
  • string Indexid2name(int tableId, int index);
  • int Indexname2id(int tableId, string indexName);
  • string insstr(string text1, string text2, int position);
  • string int2str(int param);
  • string int642str(long param);
  • int IntervalMax(DateTime inputDate, DateTime refDate, int func);
  • string IntervalName(DateTime refDate, int col, int func);
  • int IntervalNo(DateTime inputDate, DateTime refDate, int func);
  • DateTime IntervalNorm(DateTime inputDate, DateTime refDate, int func);
  • int intvmax(Date input_date, Date ref_date, int func);
  • string intvname(Date d, int col, int func);
  • int intvno(Date input_date, Date ref_date, int func);
  • Date intvnorm(Date input_date, Date ref_date, int func);
  • bool IsNonEmpty(string s);
  • int LicenseCodeNum(string licenseCode);
  • bool Like(string arg1, string arg2);
  • decimal Log10(decimal arg);
  • decimal Logn(decimal arg);
  • string LookupLabel(string pattern);
  • int Match(string pattern, string text);
  • object Max(object[] args);
  • object Min(object[] args);
  • Date Mkdate(int day, int month, int year);
  • string MonthName(int number);
  • int Mthofyr(Date d);
  • Date NextMonth(Date arg);
  • Date NextQuarter(Date arg);
  • int nextTraceSequence();
  • Date NextYear(Date arg);
  • bool NullDate(Date d);
  • bool NullDateTime(utcdatetime d);
  • bool NullGuid(Guid g);
  • string Num2char(int figure);
  • Date Num2Date(int days);
  • string Num2Str(decimal number, int character, int decimals, int separator1, int separator2);
  • string ObjectToString(object o);
  • void OrderQ(Dictionary collection);
  • decimal PercentAdd(decimal amount, decimal percentage);
  • decimal Periods(decimal payment, decimal interest, decimal future_value);
  • decimal PeriodsRequired(decimal Interest, decimal FutValue, decimal PresValue);
  • decimal Power(decimal arg, decimal exponent);
  • decimal PresentValue(decimal Paym, decimal Interest, decimal Life);
  • Date PreviousMonth(Date arg);
  • Date PreviousQuarter(Date arg);
  • Date PreviousYear(Date arg);
  • decimal PricePerPeriod(decimal principal, decimal interest, decimal life);
  • object[] qdel(object c, int position, int numElements);
  • int qfind(object c, object[] parameters);
  • object[] qins(object c, int position, object[] parameters);
  • int qlen(object[] container);
  • object qpeek(object c, int position);
  • object[] qpoke(object c, int position, object[] parameters);
  • decimal Rate(decimal future_value, decimal current_value, decimal terms);
  • string remove(string text1, string text2);
  • decimal Round(decimal dbl0, decimal dbl1);
  • void SecAuthzCheck(string className, string methodName);
  • int sessionid();
  • void SetInArray(int position, T value, Dictionary array, T zeroValue);
  • int setprefix(string prefix, ref IntPtr ptr);
  • decimal Sin(decimal arg);
  • decimal Sinh(decimal arg);
  • int Sleep(int duration);
  • decimal Sln(decimal cost, decimal salvage, decimal life);
  • int Sound(int frequency, int duration);
  • Date Str2Date(string text, int sequence);
  • utcdatetime Str2Datetime(string text, int sequence);
  • object Str2Enum(object e, string s);
  • object Str2EnumExtension(object dummyParm, string valueName, string enumTypeName);
  • Guid str2guid(string input);
  • int str2int(string text);
  • long str2int64(string text);
  • decimal Str2Num(string text);
  • int str2time(string text);
  • string StrAlpha(string text);
  • string strcolseq(string text);
  • int StrFind(string text, string characters, int position, int number);
  • string strfmt(string text, object[] parameters);
  • Types StringToType(string t);
  • string StrKeep(string text1, string text2);
  • int Strlen(string text);
  • string StrLine(string s, int count);
  • string Strlwr(string text);
  • int StrNFind(string text, string characters, int position, int number);
  • string StrPoke(string arg1, string arg2, int position);
  • string StrPrompt(string _string, int _len);
  • string StrRep(string text, int number);
  • int StrScan(string text1, string text2, int position, int number);
  • string Strupr(string text);
  • string Substr(string text, int position, int number);
  • Date systemdateget();
  • Date systemdateset(Date d);
  • string Tableid2name(int tableId);
  • string Tableid2pname(int tableId);
  • int Tablename2id(string table);
  • string TabPNam(int dataset);
  • decimal Tan(decimal arg);
  • decimal Tanh(decimal arg);
  • string Time2str(int time, int separator1, int separator2);
  • int Timenow();
  • Date Today();
  • decimal Trunc(decimal arg);
  • void truncate_infolog();
  • int TryStart(ref IntPtr ptr);
  • void ttsabort();
  • void ttsbegin();
  • void ttscommit();
  • int ttscount();
  • int Typename2id(string typeName);
  • Types Typeof(object o);
  • string uint2str(int param);
  • IDisposable Unchecked(int uncheckValue, string className, string methodName);
  • int WeekOfYear(Date arg);
  • void Where(exprNode node, Common table);
  • int Year(Date d);

Saturday, October 01, 2016

Tutorial: Label printing in Microsoft Dynamics AX 7

A while back I posted a tutorial on how to configure and use the label printing functionality in Microsoft Dynamics AX 2012 R3:

http://kashperuk.blogspot.dk/2014/09/printing-labels-with-new-warehouse.html

With the release of Microsoft Dynamics AX 7 and the move to the Cloud, printing has become a bit more complicated than before, since the printers are not on the same domain / network as it used to be when everything was installed on-premise.

The major change that I will talk about in this post is the way we set up printers now.

Installing & configuring the Document Routing Agent

The wiki article describing the installation and configuration of the Document routing agent is very well written and contains a lot of details about the restrictions and requirements for this to work, so I will not repeat it here. Here is the link:


For example, one of the unexpected requirements is that Adobe Acrobat Reader is installed.

Note, that with a recent update of the AX platform, the Document Routing Agent can now be run in the background as a Windows service, which adds a number of benefits. Read more about that in the below wiki article:



Now that the document routing agent application / service is installed and can sign in to AX, you can select the printers you want to expose, activate them in the Network printers form, as described in the above wiki article.

After that, they will show up in the Printer name lookup on the Document Routing form as before.

Everything else is pretty much the same from a user standpoint, nothing changed in terms of document routing configuration or the WMDP configuration.

So when a label needs to be printed, here's what happens now:
  1. The label is generated in the ZPL code, as before, containing all the replaced variables from the document routing layout
  2. It is then saved in a file in the Azure Blob Storage (since the AOS does not have access to the client machine, we cannot just go and do something with it) in a pre-configured folder, including all the relevant settings, specifically, which printer to use for this label
  3. The Document Routing Agent application / service on the network printer server or just as a local application one of the network PCs will periodically query if there are any pending files to be printed, downloads them from the Azure Blob storage and, depending on the printer settings defined for the specific file, re-routes the file to be printed, whether that is to a Zebra printer, or, in the case of regular SSRS reports, to a PDF document, or a regular printer.

Note that before Platform Update 2 there was an unpleasant bug in this framework, which prevented printing of labels from the Warehouse Mobile Devices Portal. That has since been fixed, and you should be able to print labels without any problems (Workaround for people on earlier installations is to use the WMDP enumlator form from AX web client)

Give it a try and report back here in case you find some of the instructions unclear, or if something is not working according to your expectations.

Thanks!

Friday, September 30, 2016

Announcement: Dynamics 365 general availability is just around the corner

On Wednesday, July 6 we announced Microsoft Dynamics 365, the next generation of intelligent business applications in the cloud.

Now it is time for the Dynamics 365 public launch event.

Join online on Tuesday, October 11 at 11:30 AM Pacific Time (UTC-7) and be among the first to see our next generation of business applications—helping your organization to grow, adapt, and evolve.

https://www.microsoft.com/en-us/dynamics/dynamics-365-first-look

We hope you love it as much as we do!

Tutorial: Location directive failures - Common mistake #1 - Multi SKU

Preface

A lot of people that are getting to know the new Warehouse solution are struggling to set up the location directives correctly. They very often end up scheduling some work and not getting the location automatically selected by the system.

A good example is this post on the AX community where I attempt remote location directive setup data troubleshooting: https://community.dynamics.com/ax/f/33/p/141641/309594#309594

In this next series of posts I want to cover some of the more common errors people do when setting up the location directives and explain ways you can try to figure out why a location was not pre-selected automatically, if that happens.

You can read the basic information about creating location directives on MSDN, in these posts I will assume good knowledge of the concept.

Common mistake #1 - Multi SKU

This is one of the most common problems reported as "location directives are not working and I can't figure out why, everything seems right".

As most of you have probably seen on your installations, you needed to setup 2 location directives for Put for each configuration, one with Multi SKU, the other without. Often, because people don't really understand when this setting comes into play, they don't create both, but only the single sku, or only the multi sku, thus resulting in location not being assigned (allocation failing).

So here is a brief explanation:

As you know, when work is being created for an order, whether it is a sales, a purchase or any other supported order, the location directives are used to determine WHERE to pick/put the item(s). So for each individual work line location directives will be evaluated, and a location will be selected based on the work line details. The location directives will be evaluated sequentially from all the ones that meet the requirements for the particular work line (e.g., they are for the right warehouse, have the right details based on the query, etc.)

Note. There are special cases where the location to Pick/Put is pre-selected. For example, during PO registration the first pick is always from the RECV location. Another example is the Inventory movement by template, where the location to Pick from is selected by the worker himself, and only the puts are done through location directives.

One of the criteria evaluated on the location directive is the Multi SKU flag. The location directive which has Multi SKU flag selected will only be considered, if the corresponding work line being processed right now has Item number = 'Multiple' (it shows up as blank in the work details, but as "Multiple" on the mobile device). This is typically the case for when you need to put items down to a Staging / Pack / QMS areas, after having picked up multiple items from the different picking locations in the warehouse.

Therefore, if Multi SKU flag is selected for a location directive, you cannot edit the query for it. That makes certain sense, since this location directive would only apply to multiple items, thus you cannot really check if they fall withing the conditions of the query, because there are more items than one.

The Multi SKU flag mostly makes sense for Put location directives, because for most of the picks we are in a situation where it is:

  • an initial pick (thus, pick of 1 particular item)
  • a special pick of one or more items from a pre-defined location (like RECV I mentioned above)
  • a special pick where we continue work execution after having previously put the item(s) down to a certain location (STAGE is the typical example, since we'll need to continue to BAYDOOR after that)

Location directives do not prevent you from setting the Multi SKU flag for Picks, however, so if there's a certain scenario where you do need this, it'll be available.

Short summary

In short, if your work orders are always small, 1 item per order, then you do not need a Multi SKU location directive.
If, however, you have work orders with single items, and then some with multiple items being picked as part of one work order, you will need to have 2 separate location directives, one with and one without the Multi SKU flag, and then you can decide if you want the goods travelling to a different location in such cases.

Example walkthrough

To show the difference between the behavior of the location directives with and without Multi SKU, I have created the following location directives, which are identical apart from the flag, as well as the location to Put, so we can see the result difference. Again, only the two Put location directives are really of interest. I have chosen to show a Sales scenario, but the same applies for all other flows.

Location directive for sales Pick work lines on WH 24
Location directive for single-item sales Put work lines on WH 24

Location directive for multi-item sales Put work lines on WH 24
To summarize, the intention is that work order with single items will be put to BAYDOOR location, while multi-item orders will be put down at BAY_MULTI location.

Note. Both Put location directives have the Directive code set to Baydoor. Discussing the behavior of this field is outside the scope of this blog post, but will be covered in one of the following posts.

Now, I have created the following 4 sales orders and released each one individually to create work. I have created sufficient on-hand for picking these items. Sales orders have the following configuration:

  1. 1 line for a single item
    1. 10 pcs of A0001
  2. 2 lines for different items
    1. 10 pcs of A0001
    2. 10 pcs of A0002
  3. 2 lines for the same item
    1. 10 pcs of A0001
    2. 10 pcs of A0001
  4. 2 lines for different product variants (meaning, same item number, but different product dimensions)
    1. 10 pcs of P0004, Size = L
    2. 10 pcs of P0004, Size = S 
You can see the screenshots for work orders created, in the above order, below:

Work for sales order with a single item
Here, location BAYDOOR was used. That is correct, since the Put work line is for a single item, A0001.

Work for sales order with multiple different items
Here, location BAY_MULTI was used. That is correct, since the Put work line is for two items. You can see the Item number is blanked out on the work order line in this case.

Work for sales order with multiple lines for same item
Here, location BAYDOOR was used. That is correct, since the Put work line is for a single item, even though the quantity for this line is a combination of more than 1 work line.

Work for sales order with multiple product variants
Here, location BAY_MULTI was used. That is correct, since the Put work line is for multiple product variants. So even though the Item number stamped on the Put line is just 1 item, the product dimensions are blanked out since there is more than one.

This is one of the "confusing" parts, as the rule is not just a simple "item number is not filled in".
We treat product variants as different products in this regard, so it is important to note this particular difference.

Next step

Stay tuned for the next common mistake in one of the next posts.

Tuesday, September 27, 2016

Development tools: Editor scripts in Microsoft Dynamics AX 7 or Visual Studio X++ code snippet library

As you know from some of my previous post, I'm a bit fan of developer productivity. Over the years I've delved into creating new Editor Scripts in Axapta, some of the more complex ones shown below:

http://kashperuk.blogspot.dk/2008/08/another-useful-editor-script-for.html
http://kashperuk.blogspot.dk/2010/01/editor-script-for-simplifying-search-in.html
http://kashperuk.blogspot.dk/2008/05/editorscriptsaddinsopeninaot-version-2.html

That's why I was very happy to read a blog post by Martin Drab aka goshoom the other day. Martin describes the use and the simple steps to create new code snippets for Visual Studio that apply to X++ language. Here it is: http://dev.goshoom.net/en/2016/06/custom-code-snippets/

Now, as you can see from the blog post, it's not exactly the same in terms of capabilities as the Editor Scripts in AX 2012 and prior. The code snippets in VS are specifically targeted at inserting some new code into the editor or refactoring existing code in the editor, but not interacting with AOT and AX metadata.

They are however still quite handy, especially if you get used to the hotkey combination Ctrl+K, Ctrl+X (or S for surrounding your code with whatever the snipped inserts).

That's why I decided to create a public code snippet library through which we could potentially share some useful code snippets with each other.

To start it off I have ported some of the more commonly used template editor scripts from AX 2012.

Here is the link to the Visual Studio X++ code snippet library.

Share your cool code snippets here in the comments, and I will upload them to the share (just to have some moderation process in there)

If you have some comments or suggestions as to a better place to store these, let me know through the comments below.

Thanks!

Monday, September 26, 2016

Development tutorial: Extensible base enumerations in Microsoft Dynamics AX 7

Introduction

Microsoft Dynamics AX 7 is a game changer when it comes to applying customizations required for running your specific business. Specifically, the development approach changed to focus a lot more on extending the application instead of over-layering elements to customize application flows.

Over the following months I will try to describe some of the examples of where Microsoft has ensured that the application can be extended without modifying existing AOT elements.

If there is a specific topic you would like me to cover, let me know in the comments.

Topic of today

Today I would like to start by talking about Base Enums. Enums in X++ are defined in AOT to represent a list of literals, or named constants, if you will, which then can be used in the code in a convenient way. They are stored as integers in the database.
As all of you I'm sure are already familiar with this concept, I will no go further into this.
If necessary, here's a refresher: https://msdn.microsoft.com/en-us/library/aa881702.aspx

Base enums have caused a lot of grief when writing customizations or when doing code upgrade, as you always needed to be aware of new values Microsoft might add to the list in future updates, some of the ISVs could add to the list, etc. So normally you'd have to remember to add a gap in values when adding your new enum value.
You also needed to be very careful about changing existing values as to not break the existing data in the database.

In AX 7 it is not that easy. Since there is a much more strict separation into models, with Platform and Foundation pieces being clearly isolated from Application components, customizing by overlayering base enums is not as easy, and not possible in some cases.

Example

Let's look at NumberSeqModule base enum as an example. You all are most probably familiar with this enum, since that's where you would need to add your new application module for new solutions, if you want it to be handled through the common Number sequence framework.
In AX 7 this enum is defined in the Application Platform model, meaning that it cannot have any values that are application-specific, as well as that it cannot be over-layered.

That means that the only way to go is to extend the enum.
If you are not familiar with the concept of extensions in Dynamics AX 7, you can read a good description on our wiki page: https://ax.help.dynamics.com/en/wiki/customization-overlayering-and-extensions/

In order to be able to extend a base enum, it needs to be marked as allowing extensions, which is done through the property IsExtensible.

Note that in Application Platform there are a few enums that are extensible, while the majority does not allow it. That is done on purpose, for performance reasons, based on the belief that partners would not need to extend these enums.

If that is not the case, you should let Microsoft know, so they mark the corresponding enum as extensible and release it as a hotfix.

As a result of extending an enum, a new base enum element will be created in the current model, and you will be able to add one or more element values to it.

At runtime AX will collect the values from the base definition and all extensions across all models and present the combined list of values to the user.

Note that you cannot change the properties of the base enum through extension, only add new enum values.

Difference

The major difference of an enum that has extensions vs the legacy enum is in the way how they are represented under the hood.

The extensible enums are represented in CLR as both an Enum and a Class, where each enum value is represented as a static readonly field. So accessing a specific value from the above enum, say, NumberSeqModule::Invent would under the hood look something like NumberSeqModule_Values.Invent, where Invent is of type NumberSeqModule which is an Enum. It would in turn call into AX to convert the specific named constant "Invent" to its integer enumeration value through a built-in function like the symbol2Value on DictEnum.

You'd need to add a using statement to your project, as shown below:
using Dynamics.AX.Application.ExtensibleEnumValues;
This class containing all the values would be built based on all the extensions of the corresponding enum, thus providing a generic way of handling them.

Nothing changes on the database layer, the values are still stored as integers, as today.

X++ impact

  • What this means for your every-day X++ development is that things like comparison of different enum values are out of the question. So you cannot, for example, write
if (workTable.WorkStatus <= WHSWorkStatus::InProgress)
You would now need to be more explicit, listing all the specific values that are applicable through an equality operator. (Like, Open and InProgress)
  •  This also means that you cannot rely on a certain enum value having a specific integer value, since they will be assigned per-deployment depending on all the different extensions for this enum you have deployed. So doing enum comparisons to their integer values is a Big No from now on.
if (workTable.WorkStatus == 0)
Note that both of the above have been a Best Practice for enums for a while now, so your modifications should hopefully not be impacted, if a certain enum is made extensible.
  • For extensible enums there is still an implicit conversion to and from integer type, but a warning will now be shown, to warn the developer that the enum value might be incorrect as a result of the assignment.

Development environment impact

In Visual Studio the impact is visible for any kind of metadata properties where enum values need to be specified. That includes Fixed field and Related Field Fixed when defining relations on tables and data entities, filter values on Query data source ranges, etc.

For example, when defining a Fixed field relation, you would now be able to select the enum value from a dropdown:

Setting value of an extensible enum on a Fixed field relation

Note how the full notation is used instead of what you'd do before with specifying the integer value of the corresponding value.

On query data source ranges you would instead use the short notation by just specifying one or more enum values, comma separated:

Setting value of an enum on a Query range

Conclusion

As you have seen, extensible enums is a way the platform now supports adding additional values to existing base enums without customizing these enums, which really simplifies upgrade going forward.
It however introduces certain limitations on how you use these enums in X++ code and metadata that you need to familiarize yourself with.

Let me know if you have any questions on this

Saturday, September 24, 2016

Tool: Dynamics AX 7 browser power & Table browser add-in for Google Chrome


Microsoft Dynamics AX "7" is, as you all know, a Cloud release, which means that the one and only client available for AX "7" is the web browser.

Nowadays browsers are pretty advanced, meaning that you get pretty much the same look and feel, as in a "rich" Win32 client. It has its drawback as well, of course, but we are not here to talk about those.

On the positive side, it now allows for a number of entry points that will take you directly into the flow or form you want to execute, because most of the information about where you want to go is provided through the URL itself.

I have already previously posted about the Warehouse Mobile Devices Portal emulator form and how you can access it. Here it is again:

https://usnconeboxax1aos.cloud.onebox.dynamics.com/?cmp=USMF&mi=action:WHSWorkExecute

You basically specify your Dynamics AX URL, followed by which company you want to connect to, and which menu item to open - in this case, an Action menu item WHSWorkExecute. Easy, right?

Well, you can do the same trick to open other menu items, and that is what this blog post is about - Table Browser.

As some of my old readers know, I'm a big fan. If you are not one of those, and are running previous versions of AX, check out the blog post below, it's awesome!

http://kashperuk.blogspot.dk/2007/09/devsystablebrowser-version-20-is-out.html

Well, Dynamics AX "7" also has a little something to help you browse tables now.
Here is a cool little add-in for Google Chrome, which allows you to open the table browser for a selected table in a selected company (settings are persisted, so you don't need to enter the company name each time):

https://chrome.google.com/webstore/detail/ax-table-browser-caller/nahbldacmaibopfiiaoboloegpobpccn

I've just tried it out and it's pretty neat, so take it for a spin and let me know what you think!

To finish off, let's just take a quick look at what the add-in actually does, which is - opens a pre-defined URL similar to WMDP link above:

https://usnconeboxax1aos.cloud.onebox.dynamics.com/?mi=SysTableBrowser&TableName=WHSWorkTable&cmp=USMF&limitednav=true&lng=en-us

We specified that we want to open the Display (which is the default) menu item SysTableBrowser, passed in an argument TableName WHSWorkTable. We also specified the company, as before, as as well the display language. We've also added in the limitednav=true flag, which removes some of the navigation panels and buttons, so you cannot navigate away from this page to another form or menu.

Technical Note. Check out UrlUtility.getQueryParamValue('TableName'); This could potentially be used in some of your customizations

Wednesday, July 06, 2016

Announcement: Microsoft Dynamics 365 and Microsoft AppSource

Today Microsoft made a big announcement related to Dynamics and its future.

Please take the time to read the announcement and comment below on what you think this means and how important of a change it is for us.


Thanks!

Saturday, February 20, 2016

Announcement: Microosft Dynamics Salary Survey 2016

As you saw from my last post, Tech Conference is happening next week.
That is a great opportunity to mingle with other partners and developers, potentially finding that ideal place of work you have always dreamed of.

In that context, consider participating in the Microsoft Dynamics salary survey carried out each year by Nigel Frank.
I always enjoy reading through and comparing all the numbers and just the general stats are pretty fun.

Here's a more detailed description of the survey from the organizer. Note some nice Microsoft branded prizes for 3 random participants. It's free to participate, and the chance to win is pretty good, considering the number of Dynamics specialists out there. Go for it!

Take the Survey Now


Find out How Your Salary Compares to Others in Microsoft Dynamics Roles. Take Microsoft Dynamics Salary Survey 2016

Take part in Nigel Frank’s industry acclaimed annual Microsoft Dynamics Salary Survey in its 7th edition and find out how your remuneration compares to others in similar roles. You will automatically be entered into a prize draw to win a  Microsoft Surface Pro 4, Xbox One or Microsoft Band 2.

Once the Salary Survey report has been compiled, you'll be one of the first to receive it via email. The results of the survey give an unparalleled insight into global salary trends for Dynamics professionals. It will allow you to benchmark your team’s, company’s and your own salary against your peers.
Follow this link to take the survey
http://goo.gl/Hfsw6N

Thanks

Monday, February 16, 2015

Microsoft Dynamics Salary Survey 2015

Participate in the survey
Win one of these cool prizes!

Find out How Your Salary Compares to Others in the Industry. Take Microsoft Dynamics Salary Survey 2014-2015


Take part in Nigel Frank’s industry acclaimed annual Microsoft Dynamics Salary Survey and find out latest Dynamics compensation trends. You will automatically be entered into a prize draw to win a Microsoft Surface Pro 3, a Nokia Lumia 1520 or an Xbox One.




Once the Salary Survey report has been compiled, you'll be one of the first to receive it via email. The results of the survey give an unparalleled insight into global salary trends for Dynamics professionals. It will allow you to benchmark your team’s, company’s and your own salary against your peers. 

Friday, January 20, 2012

Microsoft Dynamics Salary Survey 2012 - Please participate

I don't usually make posts like this, but this is one of those things I always look forward to reading when it comes out, so I decided helping the guys out won't hurt :)

What I am asking from you guys is some time (2 minutes or so) to fill out the survey below about how much you make, what kind of benefits you get, where you work, and so forth. It's anonymous, obviously, and every participant will get a copy of the report afterwards. And this time around, you can even win something for participating! So please do.

Participate in the survey about Dynamics
Microsoft Dynamics Salary Survey 2012
Nigel Frank International would like to invite you to complete our annual survey of global Microsoft Dynamics salaries. The survey will only take a couple of minutes to complete and your response and any personal details will be kept strictly confidential.

Complete the survey by the closing date and you will automatically be entered into our prize draw to win one of five amazing prizes:

1st Prize = Apple iPad2 16gb with Wi-Fi + 3G

2nd Prize = Microsoft Xbox 360 250gb + Kinect
3rd Prize = Kindle Keyboard with Free 3G + Wi-Fi
4th Prize = Microsoft LifeCam Studio Webcam
5th Prize = Microsoft Arc Touch Mouse

You will also receive a FREE copy of the Salary Survey report once it has been compiled.