Code highlighting

Tuesday, April 13, 2021

[Development tutorial] Extending the work list and work pick line overview flow fields


Whenever we talk about the work list, and recently also the work pick line overview feature, customers think this could be a great optimization for their warehouse, but frequently, they want to display some business-specific information on the cards, that would help drive the decisions, but that information is not available out of the box on the work list.

In this post I'd like to cover the simple steps needed for extending the out of the box functionality, adding fields displayed on the work list / work pick line overview. This in turn automatically means you get sorting capabilities on these fields on the mobile device, enabling faster navigation.

If you are not familiar with these features, I'd like to refer you to the following links:

Customization walkthrough

For this demo, I've made the following extensions (in a new model/package):
  • Added a new string field, MyOwnField, to WHSWorkTable.
    • I also extended the form to display this field, so a user can modify it for an existing work order
  • Added a new display method, requiresForklift, to WHSWorkTable
    • This is a common requirement we hear from customers - being able to quickly identify if special equipment is required for performing a work order.
    • Note. I use a string as a return type instead of an enum, as the base logic does not handle enums "the right way" for display methods, thus presenting their integer representation on the mobile device, instead of the corresponding label.
    • I also needed to extend the populateWorkDisplayMethods method on table WHSTmpFieldName, so my newly added display method shows up in the list, when configuring the mobile device menu item.
  • Added a new integer field, MyOwnField, to WHSWorkLine.
  • Added a new display method, crushabilityWeightPerUnit, to WHSWorkLine
    • This allows to stack the items on the pallet (Target LP) so that the top layers do not crush the bottom ones.
    • No need to modify the lookup logic here for configuration, as it's already generic.
All of my implementations are super simple, just to demonstrate the concepts and the process. You can download the full code on GitHub.

Alternatively, watch the below YouTube video, where I walk through each customization in detail.

Result screenshots

After making, building and synchronizing those changes, I can open the mobile device and would have the following view. (I won't go over the demo data setup here)

Work list, sorted to show work that needs a forklift first. My field is also populated for some orders:

Work list with custom fields

Work pick line overview, ordered to show the items that should be placed on the bottom of the pallet first:

Work pick line overview

Question to you

What type of information are you using on the work list / work pick line overview?
Should it be available out of the box?
Let us know!


Sunday, March 14, 2021

[Tutorial] Work pick line overview


Work pick line overview is a feature that is currently in Public preview, and will become generally available in one of the upcoming Dynamics 365 SCM releases.

It provides the flexibility to experienced warehouse workers to change the work picking route on the spot, based on the current situation the system is unaware of. 

For example:

  • some of the goods on the order might need to be rushed out the door, to make the delivery truck departure time
  • system is not configured to suggest an optimal picking route that takes into account item dimensions, weight, etc.
  • worker is inhibited in some way, for example, cannot carry larger items right now, so would like to pick the smaller ones first, and then come back for the larger ones with a forklift, etc.
Using the work pick line overview feature, the worker can get all of the work lines to show up in a list on the mobile device, allowing the worker to order them based on the data displayed. This way, he can quickly choose the work line that he will pick next. 

This is a lot more convenient compared to the existing Skip button functionality, where the worker could skip one line after another until he reaches the one he would like to pick next.


As I mentioned, the feature is in Public Preview as of writing this post, so you need to first enable it through the Feature management dashboard, as shown below:
Work pick line overview feature

Enabling the feature adds a new option to the Mobile device menu items of type "Work - use existing work", like shown below. 

Mobile device menu item configuration

The 4 options available for selection in the Show work list list option are described nicely in our article about the feature on

  • Show only upon request – Workers can choose to view the pick line list by selecting the Skip to button in the warehouse app.
  • Show at the start of every pick – Workers see the list every time that they start or finish a pick line. They can also view the list again by selecting the Skip to button in the warehouse app.
  • Show at the start of the first pick only – Workers see the list every time that they start new picking work, but not after each line. They can also view the list again by selecting the Skip to button in the warehouse app.
  • Never show – The standard Skip button appears in the warehouse app, and display of the work line list is turned off. The Skip button lets workers cycle through the lines one at a time, in a fixed order. They can also cycle through the list as many times as they require, until all lines have been processed.
Selecting one of these options also make the Field list configuration button available. In the form that opens the superuser can configure the work list fields that should be visible on the mobile device. 

Note You can configure to display not only table fields, but also display methods, allowing for a much more flexible setup

Work pick line overview field list configuration


To demonstrate, how the work pick line overview can be used, I've create a small sales order with 4 lines, as shown below:

Sales order lines

Upon releasing this order to the warehouse, the following work order was created:

Work order lines

Using the new Warehouse management app (you can read more about it in my previous post), we can now navigate to this work order, using the newly created mobile device menu item, as shown below:

Work picking flow, step 1

In my example, I have configured not to trigger the pick line overview to appear automatically, meaning that the worker would need to manually select the Skip to button from the ribbon

Select Skip to button in the ribbon

This will bring up the work pick line overview screen, where the worker can see all the work lines in one or more columns, depending on the device form factor. 
All the selected fields and their respective values are shown on the cards, and tapping either of them will take the worker to start performing the pick actions for that work line.
Work pick line overview

To speed up the selection process, especially in cases where many work lines are displayed at the same time, the worker can use the ordering options available above the cards, for example, to show the heaviest items first, as shown below.

The ordering selections will also be preserved for whenever the pick line overview is opened next time, simplifying the picking process further.

Work pick line overview showing heavy item first

Demo recording

If you prefer watching demos with voice-over over reading, please check out the YouTube video I made for this feature:


As you can hopefully see by now, the Work pick line overview is a powerful feature than can help your business find further cost reductions in the warehouse by allowing more flexibility on the floor.

We love feedback, so don't be shy and let us know if you have issues with the feature, or have further ideas on how this can be improved!

Tuesday, February 09, 2021

[Ann] Warehouse management app for Dynamics 365 SCM is in Public Preview

The day has come when we are ready for Public preview of the reworked Warehouse management app. 


The Warehouse Management app is a complete remake, using the “frontline worker” visual style, as seen in Production floor execution and HoloLens Guide. The new design concepts are based on extensive usability studies including a broad worker population. The solution is designed to help workers be more efficient, productive, and better able to complete work accurately.

The new solution provides the following benefits and capabilities:

  • Faster task execution
    • “Spinner” for fast quantity input
    • Buttons easy to hit with gloves
    • Clear text on dirty screens
    • Buttons in best corner for user’s grip
  • Easier ramp-up of temp workers
    • Title and illustration per step
    • Full-screen photo to verify product
  • Solves top user pain points
    • Menu hard to use
    • Gray text hard to read
    • Hard to learn what step means
  • Settings sync’ed from FnO
    • Instruction per task step, company can edit
  • Great foundation for next steps
    • Meets WCAG 2.1 accessibility with focus on situational disabilities
      • Scales text to 400%
      • Scales buttons to 200%
    • Scaling to fit any device type
      • Based on resulting sizes of elements
      • Swaps parts
      • Changes layout sequence


You can watch a quick overview of the new app in below video recording (by Markus Fogelberg, our PM):

Or go in for a more in-depth look at some of the scalability / user configuration features available in the new app (also recorded and voiced over by Markus):



There's still work to be done to iron out the kinks here and there, but we are proud of what we've been able to build here, and would love to hear your feedback on it, so please don't be shy :)

The best way to reach us for this is through Yammer - if you are not familiar or do not know how to connect, reach out to me in the comments or through other community sites, and I'll set you up!

Tuesday, November 03, 2020

[Tutorial] Piece by piece receiving process with Advanced Warehouse Management in Dynamics 365 SCM


In a recent post (Piece by piece picking process with Advanced Warehouse Management in Dynamics 365 SCM) I described the configuration necessary to allow performing picking in the warehouse by scanning each SKU one by one aka "piece by piece". I suggest you familiarize yourself with this post as I will be re-using the master data setup here. 

In this post, we will be receiving a purchase order with 4 lines, all for DemoShoe product, with similar variants (Size and Color) and quantities, as in the prior example with a sales order. Below you can see the PO lines in full:

Purchase order with 4 lines for DemoShoe

Standard receiving flows like Purchase order item receiving, load item receiving and similar, which receive one purchase order line at a time do not support the piece by piece item entry. 

But there is one mobile device receiving flow that does. It is Mixed License Plate receiving, and below you can see how I have configured it in the application - pretty much a default configuration:

Mixed LP receiving mobile device menu item

Mixed LP receiving is meant for receiving a pallet with mixed items, as described in more detail in our team's blog post. As you can read in the blog post, the initial feature was built with a slightly different purpose, but can be used as well as described in this post.

As before, and very important, we need to configure the bar codes for scanning for all the variants. Exactly the same as in the previous post:

Product variant bar code setup for DemoShoe


1. We start by scanning in a license plate. This will be the license plate we are going to use for putaway, and as such will be set as the target LP on the created work order.

Enter License Plate

2. As Mixed LP receiving allows receiving items from multiple purchase orders, we then need to enter the PO number we are going to be scanning items from. 

Enter Purchase order number

3. Now we have reached the page where we can start scanning in the barcodes. That's exactly what we are going to do, by scanning in 38White, corresponding to 1 unit of the DemoShoe product variant of size 38 and color White.

Scanning the first product variant

4. After scanning the item we do not see any confirmation, but are presented with the same screen, allowing us to scan the next variant barcode, in this case, 38Black.

Scanning the second product variant

5. At any point in time during the process, we can view the list of items already scanned, change PO number to receiving another purchase order, or complete the scanning for current LP, in our case, when it's full and we want to commence putaway operations. 

LP Complete is what we are going to select below, after having scanned a few more items

Mixed LP receiving - Available actions

6. When completing a license plate, we are first presented with an overview of all the items, and we can still edit the list by adjusting the quantities or removing some of the items from the list. 
(Note. Similar activities can be performed from the web client)

Preview of the scanned items before completing LP

7. When we confirm the list by selecting the checkmark action at the bottom of the page, work will be generated for all the scanned purchase order lines with the respective quantities. This will use the same location directives and work templates as when scanning items in the "standard" way.

Note, that this can take some time, more than it does for a single line receipt, as this will be doing the same action N times, for each PO line we scanned. 

Maybe In future versions of Dynamics 365 SCM we will allow you to configure this process to run "deferred", so that the receiving clerk can move on to the next pallet / purchase order

While the process is running, the app will be blocked and the standard progress icon will display, as shown below:

Progress of creating receiving work

8. Once the process is complete, we will be directed to proceed with scanning the next license plate / purchase order information.

We can review the created work order in the Dynamics 365 SCM web client. 
Nothing unusual here, just a regular work order, as you can see below:

Work order for purchase order


Now you are familiar with "piece by piece" scanning operations on both inbound and outbound side.
In one of the following blog posts we will review the options available when doing inventory counting operations. 

As for now, let us know if this feature is useful for your business. What's missing? 

Anything else that you would like to see described?


Wednesday, October 28, 2020

[Tool] Display contents of an X++ container from SQL database field

Almost 15 years ago AndyD from AxForum blew me away with a post that was giving a glimpse into how the X++ containers are stored in the database. 

Ever since then I had a TODO for myself to write a blog post explaining this in detail and covering the various types that can be stored, basically allowing to review the contents of the container from DB without writing X++ code.

Luckily, today is the day! As they say, 

A colleague of mine, Maciej Plaza, has written a stored procedure, that shows the contents of a container stored in a database field. 

Some inspiration was taken from the following blog post too:

Here's an example of how you could use this to see the batch task's infolog message contents:

declare @bin as varbinary(max);

select top(1) @bin = info from batch where recid = <recid>


Here is how the output looks like:

Browsing the X++ container contents in SQL

Here is the stored proc (Also available as a file in OneDrive):

DECLARE @pos AS int;
SET @pos = 1;
DECLARE @offset AS int;
DECLARE @indent as int;
set @indent = 0;
declare @result as varchar(max);
IF SUBSTRING(@container, @pos, 2) = 0x07FD
	set @result = '<container>' + char(13);
	set @indent = @indent + 2;
	SET @pos = @pos + 2;
	WHILE @indent > 0
			IF SUBSTRING(@container, @pos, 1) = 0x00 --STRING
					set @result = @result + replicate(' ', @indent) + '<string>'
					SET @pos = @pos + 1;
					SET @offset = 0;
					declare @string as varchar(max);
					set @string = '';
					WHILE SUBSTRING(@container, @pos + @offset, 2) <> 0x0000
							SET @string = @string + 
								CHAR(CAST(REVERSE(SUBSTRING(@container, @pos + @offset, 2)) AS binary(2)))
							SET @offset = @offset + 2;
					SET @pos = @pos + @offset + 2;
					set @result = @result + @string + '</string>' + char(13);
			ELSE IF SUBSTRING(@container, @pos, 1) = 0x01 --INT
					set @result = @result + replicate(' ', @indent) + '<int>'
					SET @pos = @pos + 1;
					declare @int as int;
					SET @int = CAST(CAST(REVERSE(SUBSTRING(@container, @pos, 4)) AS binary(4)) as int);
					SET @pos = @pos + 4;
					set @result = @result + cast(@int as varchar(max)) + '</int>' + char(13);
			ELSE IF SUBSTRING(@container, @pos, 1) = 0x02 --REAL
					set @result = @result + replicate(' ', @indent) + '<real>'
					SET @pos = @pos + 1;
					DECLARE @temp as binary(8);
					SET @temp = CAST(REVERSE(SUBSTRING(@container, @pos + 2, 8)) AS binary(8));
					DECLARE @val bigint;
					SET @val = 0;
					DECLARE @dec AS int;
					SET @offset = 1;
					WHILE (@offset <= 8)
							SET @val = (@val * 100) +
								(CAST(SUBSTRING(@temp, @offset, 1) AS int) / 0x10 * 10) +
								(CAST(SUBSTRING(@temp, @offset, 1) AS int) % 0x10);
							SET @offset = @offset + 1;
					WHILE @val <> 0 AND @val % 10 = 0
						SET @val = @val / 10;
					SET @dec = (CAST(SUBSTRING(@container, @pos, 1) AS int) + 0x80) % 0x100;
					declare @real as real;
					SET @real = CAST(@val AS real);
					WHILE @dec >= LEN(CAST(@val AS varchar)) + 0x80
							SET @real = CAST(@real AS real) * 10.0;
							SET @dec = @dec - 1;
					SET @dec = (CAST(SUBSTRING(@container, @pos, 1) AS int) + 0x80) % 0x100 + 1;
					WHILE @dec < LEN(CAST(@val AS varchar)) + 0x80
							SET @real = CAST(@real AS real) / 10.0;
							SET @dec = @dec + 1;
					IF SUBSTRING(@container, @pos + 1, 1) = 0x80
						SET @real = 0 - CAST(@real AS real);
					SET @pos = @pos + 10;
					set @result = @result + @real + '</real>' + char(13);
			ELSE IF SUBSTRING(@container, @pos, 1) = 0x03 --DATE
					set @result = @result + replicate(' ', @indent) + '<date>'
					SET @pos = @pos + 1;
					DECLARE @year char(4);
					DECLARE @month char(2);
					DECLARE @day char(2);
					SET @year = SUBSTRING(@container, @pos, 1) + 1900;
					SET @month = SUBSTRING(@container, @pos + 1, 1) + 1;
					SET @day = SUBSTRING(@container, @pos + 2, 1) + 1;
					IF LEN(@month) < 2
						SET @month = '0' + @month;
					IF LEN(@day) < 2
						SET @day = '0' + @day;
					declare @date as date;
					SET @date = CAST(@year + '-' + @month + '-' + @day AS date);
					SET @pos = @pos + 3;
					set @result = @result + convert(varchar(max), @date) + '</date>' + char(13);
			ELSE IF SUBSTRING(@container, @pos, 1) = 0x04 --ENUM
					set @result = @result + replicate(' ', @indent) + '<enum>'
					SET @pos = @pos + 1;
					declare @enum as int;
					SET @enum = CAST(SUBSTRING(@container, @pos, 1) AS int);
					SET @pos = @pos + 3;
					set @result = @result + @enum + '</enum>' + char(13);
			ELSE IF SUBSTRING(@container, @pos, 1) = 0x06 --DATETIME
					set @result = @result + replicate(' ', @indent) + '<datetime>'
					SET @pos = @pos + 1;
					DECLARE @year2 char(4);
					DECLARE @month2 char(2);
					DECLARE @day2 char(2);
					DECLARE @hour char(2);
					DECLARE @min char(2);
					DECLARE @sec char(2);
					SET @year2 = SUBSTRING(@container, @pos, 1) + 1900;
					SET @month2 = SUBSTRING(@container, @pos + 1, 1) + 1;
					SET @day2 = SUBSTRING(@container, @pos + 2, 1) + 1;
					SET @hour = SUBSTRING(@container, @pos + 3, 1) + 0;
					SET @min = SUBSTRING(@container, @pos + 4, 1) + 0;
					SET @sec = SUBSTRING(@container, @pos + 5, 1) + 0;
					IF LEN(@month2) < 2
						SET @month2 = '0' + @month2;
					IF LEN(@day2) < 2
						SET @day2 = '0' + @day2;
					IF LEN(@hour) < 2
						SET @hour = '0' + @hour;
					IF LEN(@min) < 2
						SET @min = '0' + @min;
					IF LEN(@sec) < 2
						SET @sec = '0' + @sec;
					declare @datetime as datetime;
					SET @datetime = CAST(@year2 + '-' + @month2 + '-' + @day2 + ' ' + @hour + ':' + @min + ':' + @sec AS datetime);
					SET @pos = @pos + 12;
					set @result = @result + @datetime + '</datetime>' + char(13);
			ELSE IF SUBSTRING(@container, @pos, 1) = 0x07 --CONTAINER
					set @result = @result + replicate(' ', @indent) + '<container>' + char(13);
					SET @pos = @pos + 3;
					set @indent = @indent + 2;
			ELSE IF SUBSTRING(@container, @pos, 1) = 0xFF --CONTAINER END
					SET @pos = @pos + 1;
					set @indent = @indent - 2;
					set @result = @result + replicate(' ', @indent) + '</container>' + char(13);
			ELSE IF SUBSTRING(@container, @pos, 1) = 0x2D --GUID
					SET @pos = @pos + 1;
					SET @offset = 0;
					declare @guid as uniqueidentifier
					SET @guid = CAST(CAST(
						REVERSE(SUBSTRING(@container, @pos, 4)) +
						REVERSE(SUBSTRING(@container, @pos + 4, 4)) +
						REVERSE(SUBSTRING(@container, @pos + 8, 4)) +
						REVERSE(SUBSTRING(@container, @pos + 12, 4)
					) AS binary(16))AS uniqueidentifier);
					SET @pos = @pos + 16;
					set @result = @result + replicate(' ', @indent) + '<guid>' + cast(@guid as varchar(80)) + '</guid>' + char(13)
			ELSE IF SUBSTRING(@container, @pos, 1) = 0x30 --BLOB
					SET @pos = @pos + 1;
					SET @offset = CAST(CAST(REVERSE(SUBSTRING(@container, @pos, 4)) AS binary(4)) AS int);
					SET @pos = @pos + 4;
					declare @blob as varbinary(max);
					SET @blob = CAST(SUBSTRING(@container, @pos, @offset) AS varbinary(max));
					SET @pos = @pos + @offset;
					set @result = @result + replicate(' ', @indent) + '<blob>' + CONVERT(VARCHAR(max), @blob, 2) + '</blob>' + char(13);
			ELSE IF SUBSTRING(@container, @pos, 1) = 0x31 --INT64
					SET @pos = @pos + 1;
					declare @bigint as bigint;
					SET @bigint = CAST(CAST(REVERSE(SUBSTRING(@container, @pos, 8)) AS binary(8)) AS bigint);
					SET @pos = @pos + 8;
					set @result = @result + replicate(' ', @indent) + '<int64>' + cast(@bigint as varchar(max)) + '</int64>' + char(13)
			ELSE IF SUBSTRING(@container, @pos, 1) = 0xFC --ENUMLABEL
					SET @pos = @pos + 1;
					DECLARE @value int;
					DECLARE @name varchar(40);
					SET @value = SUBSTRING(@container, @pos, 1);
					SET @pos = @pos + 1;
					SET @offset = 0;
					SET @name = '';
					WHILE SUBSTRING(@container, @pos + @offset, 2) <> 0x0000
							SET @name = CAST(@name AS varchar(40)) + 
								CHAR(CAST(REVERSE(SUBSTRING(@container, @pos + @offset, 2)) AS binary(2)))
							SET @offset = @offset + 2;
					declare @enumlabel as varchar(max);
					SET @enumlabel = CAST(@name + ':' + CAST(@value as varchar(3)) as varchar(44));
					SET @pos = @pos + @offset + 2;
					set @result = @result + replicate(' ', @indent) + '<enumlabel>' + @enumlabel + '</enumlabel>' + char(13)
			declare @errormsg varchar(max);
			set @errormsg = 'Unexpected type ' + CONVERT(VARCHAR(1000), SUBSTRING(@container, @pos, 1), 2);
			throw 50000, @errormsg, 1;
print @result

Hope this helps!