Wednesday, October 17, 2018

[Tutorial] Reading On-Hand information for Warehouse enabled items

Having difficulty understanding your own business data is probably not something you would expect to happen to you, but I know many people, including many 10+ AX experts who have a difficult time following the information about item availability when it comes to warehouse related items.

In this blog post, I will try to walk you through a specific example, line by line, column by column.

Note I am assuming that everyone is familiar with the general way we show on-hand information, specifically that you can summarize it by a different set of dimensions, viewing it aggregated by warehouse, location, or any other combination of dimensions.

What's the challenge?

The problems start when you need to account for the reservation hierarchy of the warehouse enabled item, and displaying how much is available at the different reservation hierarchy levels.

On-hand view, summarized by Site, Warehouse, Inventory status, Location and License plate

On-hand for item A0001, summarized by all storage dimensions


On-hand view, summarized by Site, Warehouse

On-hand for item A0001, summarized by Site and Warehouse


Step-by-step analysis of the data

  1. If we look at the Physical Inventory column, the on-hand there is always at the lowest dimension level – meaning we only see values for rows where you have all of the dimensions specified, down to the License plate level.
    1. That’s why in the first row, where there’s no location/license plate, the Physical inventory is shown as blank (0). Same for the 5th row, where for location FL-001 with blank LP there’s nothing in physical inventory. (RED)
    2. Note that for BULK-001 there is physical inventory, even though there is no LP – that’s because this location is not tracked at License Plate level, so the Location IS the lowest level (ORANGE)
    3. For some of the lines shown, there simply isn’t on-hand, but we used to have on-hand on them, which is why they show up. (YELLOW)
    4. The rest (GREEN) are quantities that are on license plates on corresponding locations. You can sum up and see the total quantity of this item we have in the warehouse is 205 (but, again, that’s not displayed in the first row, unless you change which dimensions are displayed – if you only display Site and Warehouse, for example, that’s what you would see – 205. See screenshot #2 above)
  2. Reservations are done at different dimension levels. So, for example, Sales order reservations are at “Above-location” level, while Work reservations are at “Location” level. Reservations at other levels are also possible
    1. That’s why in the first row, we have 30 in the Physical Reserved column – that is 3 different sales orders having a reservation against the quantity 10 each. They don’t care which location/license plate we take the items from, as long as we have enough on the warehouse/status level. (BLUE)
    2. Work reservation are at the location level. That’s what we have in row 5, where 2 work orders each reserve 10 units, and those units are to be picked exactly from location FL-001, but we do not care which license plate they will be taken from. (DARK BLUE).
    3. All the other rows are empty, as we have no reservation at the location+LP level here (Even though it is possible, generally speaking)
  3. Available physical column is where it gets a bit more tricky to understand. Let’s take it bottom up, so it’s easier to understand
    1. On third line, we have 100 physically there, and there are no reservations for any of it, so all of it is available. Thus, it’s 100
    2. Same for the 4th line, 10 there and all of it is available.
    3. Now, important part here. On FL-001, LP 24, I have 90 physically, but 20 is reserved. So why the heck is 75 available, instead of 70? (PURPLE)
                                                    i.     It is due to the reservation being on a higher level. The reservation is done at the location level, so when calculating available, it accounts for other license plates in this location as well, LP 000USMF-000..55 has 5 on it.
                                                   ii.     Another way to think of it – how many items can I take away from this License plate, so my existing reservations are still respected. The answer is – 75, because you’d have 15 left on this LP and 5 more on the other LP within the same location. The warehouse-level reservation is also still respected, because we have enough in other locations.
    1. On FL-001, 000USMF-00..55, we have 5 available. That’s what is physically there, and because we have enough in the other LP, all of this is available for taking for another purpose.
    2. The total quantity available at the location level matches what I described above, and equals 75, as it has 95 physically on it on multiple LPs, and 20 is reserved for work.
    3. On the warehouse level, we have 205 physically, and we need 30 for sales orders, so 175 is available to be reserved for other purposes. As you can see, that’s not a direct sum-up of the other rows. Same as the available quantity on FL-001 was not a sum of what is available at each LP. The calculation logic is the same here.

Additional information


If you are still curious for more after reading the below, check out this great 1 hour presentation my colleague Lennart did at a conference a while back, explaining the reservation engine and on-hand impact: https://www.youtube.com/watch?v=--_didmZKHo&t=4s

Monday, October 15, 2018

Telemetry as part of the Dynamics 365 Finance and Operations life-cycle

How much telemetry are we collecting?

A lot, like, really a lot!

That includes kernel level information, like an online user session requesting web access to a particular AOS to perform business operations through the web UI, a web service request to handle a mobile device operation, an OData request for exporting or importing data, exceptions and other infolog messages displayed to the user, etc.

It also includes specific application-level information, like details about each step during an inventory update, or information about the different wave processing steps, the specific flow happening on the warehouse mobile device, etc.

And we keep adding to it to have more and more granular information about what exactly is happening in the system at any particular point in time.

How can I access all this telemetry?

You can find it under "View Raw Logs" under Environment Monitoring in LCS.
It's very well described in the following two articles, so please read through those at this point:
You can then use the different search options described above to query out the specific events you are interested in.

What about Warehouse - specific telemetry, say, Wave processing?

Depending on the version of the product you are running, it differs slightly. 

Here's how you would search for wave processing events on releases before Fall release of 2018:


Raw logs search criteria

Note The above query options is a preview feature, so is most probably not visible to you yet.

If you then wanted to filter on a specific wave, for example, you could add that to the search criteria as well. 
Note that due to compliance, all of the information is not exposed directly, but rather RecIds are used. So you'd need to retrieve the RecId of the wave you wanted to investigate.

Here's some of the fields you should take note of:
  • The TIMESTAMP column would tell you when exactly the event occurred
  • RoleInstance would show, which AOS the activity happened on. This could be useful when troubleshooting caching and other cross-AOS issues
  • ActivityId is a way for you to limit to only a specific smaller process (for example, a specific allocation thread), and dig deeper, for example, to analyze the slow queries that happened as part of that activity
  •  infoMessage would contain information about the actual wave step performed, as well as specific details about the step, like how many load lines were processed, how long it took, etc.
TaskName: WhsPerformanceTaskStop, ruleName: waveProcessing, actionPerformed: runWaveStep, durationInMilliSeconds: 0, details: {"allocatedLoadLines":"500","custom":"no","waveId":"5637815827","waveStep":"WhsPostEngineBase.allocateWave"}

From Fall release onward

You could search for WHSPerformanceTaskStart/Stop directly in the TaskName instead of as part of infoMessage. The rest still applies.

What about mobile device telemetry - do we capture that?

Of course we do :)

Just search for TaskName == WhsUserActivityEvent
This event contains the same basic information as mentioned above, as well as stuff specific to the mobile flow:
  • Company, Site and Warehouse, where the warehouse user is operating. (RecIds)
  • WorkExecuteMode and step for the specific flow. You'll need to lookup the actual step in code
  • GUID of the mobile device user session
  • WorkTransType, as well as WorkTable and WorkLine RecIds being processed
  • RequestXML - this is the actual request, except all the data and labels are scrubbed, so it shows just what controls are shown on the screen
This event, if you search for it in code, is invoked at the very end of processing the user input from the mobile app.

You can correlate these events with those of TaskName == RequestContext, where the url of the request contains "/api/services/WHSMobileAppServices/WHSMobileAppService/getNextFormHandHeld" - this is the actual web service call as it arrives at the AOS, so the point in time when the AOS starts handling the mobile app flow step. 

Everything that happens in between with the same ActivityID is part of the flow, whether that is slow queries, error messages or other relevant events.

I don't see these events - what should I do?

We have back-ported a lot of the telemetry through hotfixes. Search for it on LCS for your specific release. 
But I would also like to take this opportunity and move up to the latest release. There is soo much goodness in there!

Should partners be adding telemetry?

ABSOLUTELY!
  • If you are a client, I suggest that you start insisting your partners adds telemetry with any new code the push into production.
  • If you are a partner, I suggest you start adding it asap - you'll save yourself a lot of time going forward, if the customer has an issue with your code. Since you cannot just debug in production any longer, you'll need to rely much more on alternative ways of telling you what exactly happened. Telemetry is the solution.

Where do we start? Do we have some examples?

We are working on the guidance for this and will share this out very soon.


Yes, all of the application telemetry is added directly as part of the application code, so should not be too difficult to find.
I suggest that you rely on the same event "InfoLogMark" as shown below as well.
Consider adding a using statement to reduce the invocation.

Microsoft.Dynamics.ApplicationPlatform.XppServices.Instrumentation.XppRuntimeEventSource::EventWriteInfoLogMark(
                                      Exception::Info, strFmt('TaskName: , etc.', );

What else should be captured out of the box that is critical for you?

Let me know in the comments!
Thanks

Wednesday, June 06, 2018

[Learning] Transportation management in Dynamics 365 Finance & Operations

I don't talk much about TMS in this blog, so I decided to correct that a bit, and post a set of educational links for those interested in starting up with Transportation management in AX.

If you are already familiar with all the concepts, you probably won't gain that much from these, but for those just starting up, I think it can provide a great overview of what the system can do.

You can start by reviewing the presentation one of my colleagues did a while back.


For a more in depth look, you can go through the TMS learning course (note it is based on AX 2012 R3, but the functionality is largely still the same in the latest release):

https://mbspartner.microsoft.com/AX/CourseOverview/1123

A couple more learning videos are available through the below link:

https://mbspartner.microsoft.com/EOP/Topic/77


Transportation management implementation guide and white papers available can be found through our SCM team blog posts:




And, finally, we have a permanent page on doc.microsoft.com, that you can access through the below link. This is where most of the upcoming functional changes and updates will be communicated going forward.

https://docs.microsoft.com/en-us/dynamics365/unified-operations/supply-chain/transportation/transportation-management-overview


And, of course, if you have further questions after this, please reach out, so we can connect you with the right people to help.

Thanks

Sunday, June 03, 2018

Extensibility: You can now log extensibility requests for Microsoft through LCS

If you weren't living under a rock for the last 2 years, you have already heard about us locking down overlayering starting with the latest release of  Dynamics 365 for Finance and Operations 8.0, and relying solely on the extension model of developing customizations going forward.

But, of course, as this is a moving target, not everything can be done through extensions in the current release.
You will discover that some customizations, which were possible with overlayering, cannot be done through extensions. To enable the same business requirements without overlayering, we have added many extension capabilities and expect to add more going forward. For some customizations that were done with overlayering, you will need to log requests, to make us aware of what you need.
So, how do you log such requests?

Starting in June 2018 you can now log extensibility requests through your LCS project.
Prior to logging a request, make sure you've evaluated alternatives, and have all the necessary information as part of the request for Microsoft to have an easier time adjusting the core code to your specific requirements.

Here's how the "New extensibility support request" form looks like on LCS:

Add a new extensibility request

Follow the below link to find out more details and read through a step-by-step guide on logging extensibility requests:

https://docs.microsoft.com/en-us/dynamics365/unified-operations/dev-itpro/extensibility/extensibility-requests

Saturday, October 07, 2017

Development tutorial: Extensibility: Replaceable in Chain of Command methods

Recently we announced a new and pretty powerful Extensibility feature, wrapping methods with Chain of Command in augmentation classes. This allows to write much cleaner extensions with fewer lines of code, as well as provides some extra capabilities like access to protected fields and methods of augmented object, easier way of ensuring a single transaction scope for standard and extension code, etc.

If you are not yet familiar with this feature, you are missing out. Go read about it:
https://docs.microsoft.com/en-us/dynamics365/unified-operations/dev-itpro/extensibility/method-wrapping-coc

There was one significant restriction applied (by design) to these wrapper methods:

Wrapper methods must always call next

Wrapper methods in an extension class must always call next, so that the next method in the chain and, finally, the original implementation are always called. This restriction helps guarantee that every method in the chain contributes to the result.

However, what this resulted in is a more complex implementation and "workaround-like" solutions in standard code to enable some of the commonly requested extension points, where the ISV/VAR would like to completely replace the standard logic with an alternative implementation that does the same or a very similar operation.


With Platform update 11 we have added a new attribute, which allows Microsoft (on request from multiple partnres), where it is justified, to decorate a particular protected or public method, allowing wrapper methods to not call next on it, replacing the logic of that method.

Here's how it looks:

/// 
/// Attribute used to enable or disable replacing a method in an extension class. 
/// 
/// 
/// Private methods can not be set to be replaceable even with the usage of this attribute.
/// 
public class ReplaceableAttribute extends SysAttribute
{
    boolean isReplaceable;

    public void new(boolean _isReplaceable = true)
    {
        super();
        this.isReplaceable = _isReplaceable;
    }
}

Example

OK, let's now look at an example of how this will be used.

Note. Since the attribute only appeared in PU11, that means that all application released up to and including Spring release 2017 do not have any methods marked with this attribute. It is only now with the Fall release of 2017 that you might see some methods being tagged this way.


Say, an ISV wanted to provide an alternative implementation for looking up Warehouses on a specified Site, more specifically, for the method InventLocation.lookupBySiteIdAllTypes().
One way to solve this could be to add a delegate, invoke it at the beginning of the method, and then check the EventHandlerAcceptResult to see if someone has replaced the implementation, in which case, short-circuit the method execution, so standard logic is not executed.

A potential implementation shown below:

public class InventLocation extends common
{
    public static void lookupBySiteIdAllTypes(FormStringControl _ctrl, InventSiteId _inventSiteId)
    {
        EventHandlerAcceptResult lookupBySiteIdResult = EventHandlerAcceptResult::newSingleResponse();
        InventLocation::lookupBySiteIdAllTypesDelegate(_ctrl, _inventSiteId, lookupBySiteIdResult);

        if (lookupBySiteIdResult.isAccepted())
        {
            return;
        }

        SysTableLookup sysTableLookup = SysTableLookup::newParameters(tableNum(InventLocation), _ctrl);
        ListEnumerator listEnumerator = List::create(InventLocation::standardLookupFields()).getEnumerator();

        while (listEnumerator.moveNext())
        {
            sysTableLookup.addLookupfield(fieldName2id(tableNum(InventLocation), listEnumerator.current()));
        }

        sysTableLookup.parmQuery(InventLocation::standardLookupBySiteIdQuery(_inventSiteId));
        sysTableLookup.performFormLookup();
    }
}

Lookups is one of the common examples, where people might was a complete replacement of the standard logic. Note that by definition that means only one of the ISV solutions can replace it. If two attempt to accept() the result, an error will be shown.
That would typically mean that a logical conflict exists between the two ISV solutions, and the VAR would need to decide which ones to use, or make it configurable somehow.

Now, let's try to see what could be done with the new attribute, if Microsoft were to apply it on this method.

public class InventLocation extends common
{
    [Replaceable]
    public static void lookupBySiteIdAllTypes(FormStringControl _ctrl, InventSiteId _inventSiteId)
    {
        SysTableLookup sysTableLookup = SysTableLookup::newParameters(tableNum(InventLocation), _ctrl);
        ListEnumerator listEnumerator = List::create(InventLocation::standardLookupFields()).getEnumerator();

        while (listEnumerator.moveNext())
        {
            sysTableLookup.addLookupfield(fieldName2id(tableNum(InventLocation), listEnumerator.current()));
        }

        sysTableLookup.parmQuery(InventLocation::standardLookupBySiteIdQuery(_inventSiteId));
        sysTableLookup.performFormLookup();
    }
}

The ISV can now in his model wrap this method in an augmentation class, provide his own implementation, and avoid calling next():

Important. 
We recommend to always make the call conditional, so that your own logic that is not calling next is only invoked for your specific case. This will make you a good citizen, that can co-exist with other ISV solutions also wrapping the same method.


[ExtensionOf(tableStr(InventLocation))]
public final class MyPU11InventLocationTable_Extension
{
    public static void lookupBySiteIdAllTypes(FormStringControl _ctrl, InventSiteId _inventSiteId)
    {
        const str MySpecialWarehouseCtrlName = 'MySpecialWarehouseCtrl';
        if (!_inventSiteId || _ctrl.name() == MySpecialWarehouseCtrlName)
        {
            // Your own logic
            _ctrl.performTypeLookup(extendedTypeNum(InventLocationId));
        }
        else
        {
            next lookupBySiteIdAllTypes(_ctrl, _inventSiteId);
        }
    }
}

Pretty easy and neat, huh?

Missing an extension point? Log it!

Again, remember, that in order to skip calling next, the method needs to be marked by Microsoft as Replaceable.
If you need a particular method to be Replaceable, or if you in general need an extension point that is not available in the latest available release, please follow the instructions outlined here to create an extensibility request for us.


Links


To review the list of features included in Platform update 11, see the What's new or changed topic and refer to the KB article for details about the customer found bug fixes included in this update.