Code highlighting

Wednesday, December 28, 2016

Configuration tip: Warehouse mobile devices portal Native application in Azure Active Directory

Setting up the Warehouse mobile devices portal is a relatively simple process, and is described in detail on the corresponding wiki:
https://ax.help.dynamics.com/en/wiki/warehouse-mobile-devices-portal-for-microsoft-dynamics-ax/

However, now and again I would get complaints around the authorization process not working properly, or asking for interactive login, both of which shouldn't be necessary for the authorization method used by WMDP.

So in this post I would like to give a small configuration tip that prevents the below error message:

Error: "The user or administrator has not consented to use the application with ID ''. Send an interactive authorization request for this user and resource.

The reason this error is shown is because the Native application setup in Azure for WMDP has access to resources that require the Azure admin to consent to that first.

WMDP app is configured to allow access to AAD resources
As you can see, some of the resources WMDP app has access to require prior Admin consent.

Recommendation: 

When configuring the Native application for WMDP in Azure, only give it permissions for the Microsoft Dynamics ERP resources.

This will ensure you don't get unexpected errors when trying to connect to WMDP.



Happy Holidays to all of you!

Stay tuned for more posts next year and subscribe if you haven't already to get updates on the latest tips and tutorials for Dynamics 365 for Operations aka Dynamics AX aka Axapta :)

Thursday, November 24, 2016

Tutorial Link: Handling exceptions the right way in X++

Michael and I have been working the last couple of weeks to uncover some of the difficult to repro bugs in Warehouse management code, and one of the things we discovered is a pattern which can lead to very unpredictable behavior when used incorrectly.

I encourage all of you to read it and make sure all of your code is up to standard.

https://blogs.msdn.microsoft.com/mfp/2016/11/24/x-the-catch/


Thanks!

Monday, November 14, 2016

Tutorial: Location directive failures - Common mistake #2 - Batch enabled

This is a blog in a series about Location directive failures.
You can find the original blog post here:
Tutorial: Location directive failures - Common mistake #1 - Multi SKU

Common mistake #2 - Batch enabled location directive line

Very often when new people start working with the Advanced warehousing solution, they struggle to notice the little check-box on the location directive lines called Batch enabled. However, it is very important when dealing with batch-tracked items in outbound flows like Sales order processing.
Here's the typical scenario when users try to set up the location directives for the first time:

They created all the necessary Pick and Put directives, decided how they want to go about the lines and different units of measure, and now they are defining the actions, which in turn means defining the query for locations to consider for pick-up/put-down.

Here's a typical and valid location directive configuration:

Valid location directive setup without Batch enabled set
After that they create a sales order, reserve the inventory, release the order to warehouse and are surprised to get the following warnings:

Release to warehouse failed for batch-tracked item P0004
Sadly, there is no indication in the work creation log that would explain why the release failed apart from the fact it could not allocate the item.

But the reason is - the location directive action was not considered a match, because only ones with Batch enabled were considered for this item.
So let's change that and try again.

Valid location directive setup with Batch enabled set
Note, that this modifies the query you can use for selecting which locations should be considered, adding the Batches table to the query, so you can, for example, select to only consider locations, where there is on-hand for a batch that is to expire more than 20 days from now.

Defining the location directive action query for batch enabled action line
Once the setup is changed, let's re-release the sales order (to show the exact same flow as before, I deleted the load line / shipment that were created before):

Release to warehouse succeeded for batch-tracked item P0004
As you can see, now all is great, and the created work looks like below, correctly picking up the items from a Picking location we defined in the location directives:

Work created for picking sales order 000785

What this means for your configuration

What this virtually means is:
  • If you do not track batches on your items in the selected warehouse, you can ignore this setting completely
  • However, if you do track batch numbers, you will most probably want to have 2 action lines for the same location directive line every single time, one for batch-tracked and one for non-batch tracked. 
    • That might be exactly what you want if you want to keep track of your batch-tracked items (perishable goods) separately from non-perishables.
    • If not, however, then it gets pretty inconvenient, and I have no knowledge of why the original implementation was done this way (Maybe somebody from BHS can answer in the comments), but we do not have any plans to change this in the near future.
Note, that for inbound flows this check-box is not considered and is not even enabled in setup. 

What's next

In the next blog post of this series we'll talk about Disposition code and how that impacts the receipt flow

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: License plate consolidation in Dynamics 365 for Operations (1611)

Introduction to scenarios

The Microsoft Dynamics AX Warehouse management module supports a number of advanced scenarios for warehouses, thanks to the flexibility offered by the concept of Work and work lines, which take care of any and all operations in the warehouse. The system has a number of complex configuration possibilities that determine how work is created. At the same time, AFTER it has been created, work is fixed and any necessary changes require a lot of manual supervisor interventions, or, in many cases, are not possible at all.

Imagine the following two scenarios:

Scenario 1

A customer orders a number of items from us. Based on our work template setup, multiple work orders are created to pick these items from the warehouse, say, some are picked from a cooled area, while the rest is coming from the regular picking area. All of the items are placed into a staging area location after picking, to be loaded on a delivery truck.

Scenario 2

A customer orders a number of items from us. A sales order is created in our system, released to warehouse, so work is created. The picking commences, and the goods arrive at the staging location, but are not shipped the same day, because the truck to pick them up only arrives tomorrow. The same evening, the same customer orders more items from us. Correspondingly, a new sales order is created and released to warehouse, creating more work. The pick is completed, and the goods are placed in the staging location ready to be loaded the next day. The warehouse manager decides to ship both shipments on the same truck tomorrow, adding the second shipment to the same load that contains the first shipment.


In both scenarios, we now have two license plates on the same load, that are going to the same customer, sitting in the same location. When the truck comes, they will need to be loaded separately one by one, even though in many cases, the items would fit perfectly fine on just one license plate.
Until now, the warehouse worker had no way to merge the two license plates together to ship everything on just one license plate, at least in the system (probably does happen outside of the system already).

In the Fall release of Microsoft Dynamics 365 for Operations (1611) we have added the ability of consolidating items on a license plate with items on another license place within the same location, where there is work behind one or more of the license plates. This is supported by a new type of mobile device menu item with Indirect Activity code “Consolidate license plates”.

Scenario walkthrough

All you need to do to enable this mobile device flow is create a new mobile device menu item that looks like the screenshot below:


LP consolidation mobile device menu item
For the scenario walk-through I have created two sales orders as below:
  • Sales order 000781 for customer US-001, which contains two lines:
    • 10 ea of item M9200 from warehouse 51
    • 15 ea of item M9201 from warehouse 51
  • Sales order 000782 for customer US-001, which contains one line:
    • 7 ea of item M9201 from warehouse 51

Sales order 000781 was released to warehouse first, before 000782 existed, resulting in the creation of Shipment USMF-000008 on Load USMF-000010.
Sales order 000782 was then added to the same Load, and also released to warehouse, resulting in the creation of Shipment USMF-000009.


The following picking work was created for these 2 shipments:

Work order details
This scenario corresponds directly to Scenario 2 I described above.

Now, for both work orders the initial picks are executed (one or different workers), and for both the goods end up at the STAGE location, awaiting loading, as shown below:

Work order details after initial pick was executed
From here on, license plates TLP_001 and TLP_002 follow the same path in the warehouse, more specifically they both will be picked up from STAGE and loaded into the truck at BAYDOOR location.

If the warehouse worker at the outbound dock makes the decision to consolidate these two license plates (currently, this is only an ad-hoc decision by the worker, planned consolidations are not supported), he can do that using the above mobile device menu item. Here is how the flow would look on the mobile device:

Step 1

You are presented with a screen, where you need to enter the Target LP. This is the License plate, where the items will end up after the consolidation.
  • This can be a new License plate. There is no way to print this new LP Tag from this screen at this point, however, so keep that in mind.
  • This can be an existing empty License plate. This could, for example, be useful, if you want to merge the contents of 2 half-pallets onto 1 empty euro pallet. Another case is if the pallet, where the items currently are placed, is damaged or “unshippable”.
  • This can be an existing full License plate, which is set as a Target LP on an existing work order with Work order type “Sales order” or “Transfer order issue
  • This should not be work that has a Container Id on the header or any of the lines.

LP Consolidation, Step 1

Step 2

Target LP was accepted, and now you are asked to scan in the LP you want to merge onto the Target LP.
  • LP to merge needs to be a target license plate for an existing work order of Work Order Type  Sales Order or Transfer Order Issue.
  • The LP to merge needs to be in the same location as the Target LP, and it cannot be the final shipping location (that’s too late, since goods are already Picked at this point).
  • The LP to merge needs to relate to the same Load as the Target LP
    • It should also have the same delivery information, if there are more than one shipment involved. Namely, the Delivery name, Customer account, Delivery address and Mode of Delivery should match.
  • The remaining steps in the flow for both work orders being merged need to match. This is done so we ensure all relevant steps are executed, for example, that the labels are printed at the appropriate time in the flow for all consolidated items.
  • This should not be work that has a Container Id on the header or any of the lines.
LP consolidation, Step 2

Step 3

When LP to merge is accepted, the worker is presented with a confirmation screen, that shows a summary of all items on that license plate. This is to help ensure that he scanned in the right LP before the actual consolidation of the two work orders happens.

In the case below, there was only one item on TLP_002, with a total quantity of 7.00 ea

LP consolidation, Step 3

Step 4


Once the worker confirms by pressing OK, the consolidation is executed, merging the two work orders together and moving all items from LP to merge to the Target LP.


The worker gets a confirmation message that the license plates were merged, and is presented with a screen where he can continue scanning in any following license plates to merge onto the Target LP.

LP consolidation, Step 4

Here is how the work looks after consolidation happens:


The work related with LP to merge, in our scenario walk-through that is TLP_002, is now marked as Closed, and by reviewing the work lines you can see the final Put step was changed, so it now points to STAGE location instead of BAYDOOR. The only thing that changed is which license plate it is put onto, namely TLP_001 instead of TLP_002.

Work order details after LP consolidation - Merge From work
The work related with Target LP, in our scenario walk-through that is TLP_001, is still In progress, and you can see the quantities on the remaining Pick/Put pair were increased accordingly with the quantity from work related with TLP_002.

Work order details after LP consolidation - Consolidated work

If you review the corresponding work transactions, you will see that an extra transaction corresponding to the load line for M9201 from sales order 000782 has been added to the Pick line to represent the additional Pick quantity.

Accordingly, the inventory transactions also reflect the fact items were moved from TLP_002 to TLP_001, and the new work reservations are based on that as well.

Mobile device menu item configuration option “Cancel remaining origin work lines

We expect most companies to run with this configuration option turned on. It comes into play in the following situation: When there are more subsequent staging steps on both work orders, this configuration will enable you to forfeit any of the steps on the work being merged from, so the steps on the consolidated work will be the ones executed for all merged items. That specifically means that any “extra” steps on the work being merged will be Cancelled.

If there are some specific reasons why all work steps need to be executed for the merged work order separately, you should not enable the configuration option. As a result, however, you will not be able to consolidate this LP to another one.

See a more detailed explanation in the Help Text for this configuration on the menu item.

For those behind on updates :)

This feature has been back-ported to Microsoft Dynamics AX 2012 R3.
You can download it from KB number 3190562


Let us know what you think of this new feature!

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);

Thursday, October 06, 2016

Development tool: Copy 'Find references' results to clipboard (to Excel) in Visual Studio for AX 7

Problem

With the release of Microsoft Dynamics AX 7 the development moved to the Visual Studio environment.

This had a lot of advantages, like the ability to use any 3rd party add-ons of various sort (which we actually have not seen that many of being applied to AX so far), all the VS goodies that come out of the box, a more familiar IDE for new developers, etc.

But it also has its disadvantages, like the Cross-References display window.

  • First of all, the indicator of whether a particular reference is writing or reading is gone. 
  • And we now display the xRefs using the standard Visual Studio "Find Symbols Results" window, which has one huge drawback as well - no way to filter on the data displayed, or copy that data somewhere else to do that.

Solution

I was bothered by this lack of functionality for a while, so I went out to find if there's an existing solution. I came across this post, which seems like a sufficient solution for the problem, in my opinion. Kudos to the author!

I have modified the project a bit to better suite AX needs, as I planned to browse the data in Excel, which has rich filtering capabilities and more convenience in navigation, and uploaded it to GitHub so anyone can use and extend it. You can also just download the executable, if are OK with the out-of-the-box functionality I will describe below.

Project on GitHub:

Executable on my OneDrive:

Installation guide

Once you have the executable, place it in a folder on your environment running Visual Studio for AX, say, C:\Tools or whatever you prefer.

Now, from Visual Studio, go to Tools and select External tools..., as shown below:

External Tools... under Tools menu in Visual Studio

Now add a new tool by clicking Add, and specify the Title, Command and Initial directory.
The Command  should contain the path to the CopyFindReferencesToClipboard executable

Add the CopyFindReferencesToClipboard tool

User guide

Using the tool is very simple. Say I wanted to find all references to the WHSLoadLine.Qty field.
I would navigate to that field and select Find references from the context menu. 

Find references to the WHSLoadLine.Qty field
This would bring up the standard Visual Studio dockable window Find Symbol Results, containing all the references to the selected table field.

Now, all you need to do is go to Tools and select the newly added tool from the list, as shown below:

Run the tool to copy the references to clipboard
After a few seconds you will get a message box to pop up telling you the references have been copied to clipboard successfully, which means you can now to and paste the data to Microsoft Excel.



Note. Since the tool uses UI-level automation to copy the cross-references, the Find references window needs to be open and visible for the tool to work.

Now, you can do whatever you want with that data in Excel.
The way I typically use it is by just showing the data as a Table, after which:
- Exclude test related files (Actual tests and Test frameworks we used)
- Filter out only elements in a certain area, like WHS
- Build pivot tables/charts, if I am doing complexity analysis for a change / feature
- etc.

Here's how it looks:

Analyze the cross-references in Microsoft Excel

Feeback

Give it a try and let me know what you think!

Sunday, October 02, 2016

Tutorial: Extending the label printing functionality in Microsoft Dynamics AX 7

Today I would like to shed a bit more light onto how to extend the label printing functionality, specifically, show how to add a new field from the work line to be displayed on the label.

This is something we have created as a demo for Tech Conf a while back, and you can still see the recording where Per and Zach from our team showcases the different label printing scenarios.
You can view the different Microsoft conference videos here.

Just as a refresher, here is a guide for how to set up label printing in Dynamics AX 7:
http://kashperuk.blogspot.dk/2016/10/tutorial-label-printing-in-microsoft.html

Technical introduction

The label printing "framework" consists of three main components:

  • WHSLicensePlateLabel table, which contains the information for the label, which is substituted into the label through the variables.
  • WHSLicensePlateLabelBuild class, which is responsible for populating the WHSLicensePlateLabel table with data for a particular work order (line).
  • WHSDocumentRouting class, which is responsible for the actual printing of the label, as well as the substitution of variable values, using the below methods:
    • initMenuFields() method is responsible for building the list of substitute fields based on the WHSLicensePlateLabel table fields
    • printDocument() method finds the specific document routing record that matches the flow criteria (e.g., warehouse, work order type, carrier, etc.), performs the translation of the label and sends it to the selected printer (Note that the label can be printed to multiple printers and you can for each printer decide, which layout to use).
    • translate() method is responsible for actually replacing any variables in the label layout with the corresponding values from the WHSLicensePlateLabel table
Let's take a closer look at the fields available on the WHSLicensePlateLabel table:

WHSLicensePlateLabel table fields
These fields represent the full list of potential variables that can be put into the label in the Document Routing layout. Depending on the item flow, some of these fields are not populated. Note that when setting up the placeholders in the Document Routing Layout form, only visible fields are displayed. That allows for storing some "plumbing" information for each record, like the PrinterSettings.

Adding placeholder variables to a label layout

Fragile Example extension

For some scenarios you might want to display some additional fields on the label, which are not part of the list shown above.

Imagine a scenario where we want to display a "Fragile" message on the label if the items on the work line are accordingly marked. 

Since there is no such field in the label, we will need to extend this functionality. And because we are working in AX 7, we will try to use Extension instead of Overlayering the elements, as we would do in AX 2012 R3.

Step 0 - Create a new model for our changes

First of all, let's create a new model, which will contain all the elements we add through extension.

Create a new model in Visual Studio
We will put this model into a new package, as we do not intent to overlayer existing AOT elements. We will reference the Application Suite package, so that we can access the elements defined in it, such as the ones mentioned above. 
Note that by default the new package will also reference Application Platform.

Select referenced packages for model FragileExample
Confirm the creation of the new model and create a new project for it. Name it FragileExample.

Step 1 - Extend the WHSLicensePlateLabel table and add a new field FragileCode

Since we want to display an extra field in the label, we need to add it to the WHSLicensePlateLabel table. In order to do that, let's create an extension of this table in the new model:

Adding a new table extension from Application Explorer
You need to have selected the right project in your Solution Explorer before you attempt creating an extension. This is to ensure that the extension is actually created in the right model.


We are now going to add a new field of base type String, and call it FragileCode with the corresponding label.

Note I am not going to bother with labels or Extended Data types for sake of simplifying the demo

Table extension for WHSLicensePlateLabel in FragileExample model
New field FragileCode on table extension WHSLicensePlateLabel.Extension
In order for the field to appear in the Document routing layouts form all we need to do is compile the new code and synchronize it against the database. I have decided to do that at build time by configuring it accordingly, as shown below:

Synchronize on build = true for FragileExample project
If we now reopen the Document routing layouts form, we can see that our new placeholder is available in the list and we can add it to the label:

Fragile code placeholder available in Document routing layouts form

Step 2 - Populating the Fragile code field on WHSLicensePlateLabel record

Now that we have the field available on the label, we need to populate it. 
As described in the Technical information section above, the label details are populated in the class WHSLicensePlateLabelBuild. Let's quickly examine how this happens:

Sequence of calls to build a license plate label
As you all know, any and all work creation and execution in Dynamics AX happens through the WHSWorkExecuteDisplay* classes, e.g., purchase order registration can execute WHSWorkExecuteDisplayPOItemReceiving, while general work execution flows can execute WHSWorkExecuteDisplayUserDirected, etc.
Within these classes the WHSLicensePlateLabelBuild class is initialized, and the buildLicensePlateLabels() public method is called, when a label needs to be generated and printed.
Depending on the flow, either the insertSingleLabelPrintLine or the insertSingleLabelMenuItem method will be invoked internally, after which the generated label is printed through the printDocument API of the WHSDocumentRouting class, as described above.

Note. insertSingleLabelPrintLine supports the flow where the Print step is part of the work template lines, while insertSingleLabelMenuItem is for the case, where the Print is configured through the mobile device menu item.

So, in order for us to populate the extra new field we need to look into these insert* methods. With the recent AX 2012 R3 changes these methods have been refactored in a way, where most of the logic for populating the WHSLicensePlateLabel table has been pulled out into a new private initLabel() method.

In Dynamics AX 7 this method has been extended (who could have done that? :)) and now has a delegate which is invoked at the end of the method, which means that event handlers can be created for it. With these minor changes, we can now extend this class by subscribing to the event of the initLabel method being invoked, as shown below:

Copy event handler code for labelInitialized delegate
We can now put this generated event handler signature into a new class in our model, as shown below:

Signature of the event handling method for labelInitialized delegate
This is awesome and is one of the new extensibility language features in Dynamics AX 7 - I have subscribed to the event of this method being called without touching the original class at all.
As you can see, instead I have a SubscribesTo attribute that subscribes me to the corresponding event in a static fashion. 

Now all we need to do is actually populate the Fragile code on the label. Let's do that:

Implementation for the event handling method for labelInitialized delegate
As you see above, the code is super simple, all we do is get the Filter code value from the corresponding item, and populate it into the FragileCode field.

Let's now compile everything and give it a go in AX web client.

You can download the VS project file from my OneDrive here.

AX flow steps and result


  1. Modify item A0001, setting Filter Code 4 to a new value called FRAGILE
  2. Create a new purchase order for 10 pcs of A0001 to WH 24
  3. Modify the Mobile device menu item for Purchase receipt to "Print label"
  4. Ensure you have a Document routing record matching WH 24 for Purchase orders
    1. Ensure you have a document routing layout being sent to a label printer
  5. Receive the above purchase order through the WMDP
As a result, you should get a new label created, which will have the Fragile code populated with the value FRAGILE, as below (I've personalized the form to show the new field):

License plate label with the populated Fragile code

Depending on your label layout you would have this message printed on the label as well.

Conclusion


With this blog post I hoped to show you how to extend the existing set of fields available on the license plate labels.

At the same time we took a look at some of the new language constructs, design paradigms and tooling that allows for a much cleaner approach to extending existing functionality, as compared to overlayering, which many are used to.

Consider extending instead of overlayering next time you need to make a change!