Code highlighting

Friday, May 28, 2021

[Announcement] Warehouse App task validation framework (RSAT for WMA) now available on Tier 2 environments

Refresher

In a recent post, I talked about testing, using the ATL framework to write easy-to-read test automation. 
This is a developer activity recommended for ISVs and partners, to ensure their customizations against regressions. 

At a customer site, if a developer team is unavailable, or for testing E2E scenarios in the system, Microsoft recommends using the RSAT tool. You can see a quick summary of those recommendations in the below picture:

Test classification pyramid

RSAT is well suited for recording and replaying tests that use the web client of Dynamics 365 SCM.
As such, it was not providing much support when it came to validating scenarios, that required the use of the Warehouse management mobile app.

To fill that gap we had shipped the RSAT for WMA a while back, officially called "Warehouse App Task Validation Framework". If you are not familiar with its capabilities and use cases, please watch the corresponding Tech Talk to familiarize yourselves.

Problem statement

This framework with the initial release was only supporting Tier 1 environments (like, DevBoxes). 

This was not ideal, as many customers wanted to run their RSAT validation on Sandbox ("perform") environments to replicate real production performance and behavior more closely. 

Also, with more and more customers being moved to Self-Service environments, they lose easy access to free Tier 1 environments.

Announcement

Starting with Dynamics 365 SCM version 10.0.19 the Warehouse App Task Validator is now supported on Tier 2 environments! 

Configuration

All you need to do to start capturing of the warehouse mobile device operations as part of RSAT is to enable this under Warehouse management parameters, as shown below:

Enable RSAT support for WMA on Tier 2 environments

You can now navigate to the Warehouse app task validation page (under Warehouse management \ Periodic Tasks) and start creating validation scripts.

Word of caution

As stated in the help text for the configuration flag, do NOT enable this on a PROD environment, as it carries a significant overhead for each operation. 

Request for ideas

The current user experience with RSAT for WMA is OK, but not great. A lot of people are timid when presented with the large XMLs.

If you have ideas on how to improve the user experience in RSAT for WMA, let us know!

Monday, May 24, 2021

[Tutorial] Find your Device ID and Warehouse management app version

Introduction

The two basic things support needs any time you log a support case is your application version, as well as your session id or similar, plus the timestamp of when the issue occurred. This helps troubleshoot the specific issue reported.

Well, with the Warehouse management app, it's pretty much the same. We'd like to know the app version you're running, as well as the Device ID of the device where the issue happened, plus the timestamp of the issue.

In this post I'll describe how to retrieve these two pieces of information, as this is not as obvious as we would hope.

Modern app

Step by step guide to find app version and Device Id

New version released

I would also like everyone to know, that we have this past week shipped the new version of the app, version 2.0.5.0. Here is the change log for this version:

## 2.0.5.0

## Fixed issues:

- Submit button incorrectly enables depending on window size.

- Slider can't proceed on smaller screens when button size is larger.

- Four button overly being cut.

- Keyboard does not support delete button.

- Brightness issue when keyboard pressed.

- Various demo data issues.

- Details page issue for numeric fields.

- Disappearing on screen keyboard on some devices.

- Various UI bugs including background color, positioning, etc.

- Improved UI with Russian language.

- Fixed various crashes.

- Calculator re-opening problem.

- [Android] Android 4.4 crash on start-up.

- Client secret not hidden in connection settings setup.

##New Features:

- Long press any text to see it fully.

- [Android] Reduced minimum scaling to 50%.

- Improved error message when missing storage permission.

- New control sequences on certain flows.


Black and Green app

Step by step guide to find app version and Device Id

YouTube



Sunday, May 23, 2021

[Development tutorial] Thoughts on test automation + more sample tests using ATL

I recently had an interesting discussion around testing and specifically Microsoft testing efforts and frameworks, namely, ATL. If you are not familiar with this framework - you've been living under a rock. Get out from under there and go read some articles about it, like the one below

https://kashperuk.blogspot.com/2019/05/development-tutorial-sample-test-tips.html

The discussion revolved around the complexity and costs of adding and maintaining tests. The partner also questioned how often we add tests, and if we can share more samples of such. You can also see his comment with a more concrete ask in the blog post above. 

Here's a partial quote:

Partners need more example for this functionality usage. Is it possible that you provide real tests for some real issues (I just took several bugs from the latest PU Warehouse and Transportation Management)

533573 Creating a sales order’s work without a pick location is not updating the on-hand figures correctly.

535311 Cannot do material consumption from mobile device for shelf life items

536899 The container contents report does not show an error if shipment does not have a dropoff address specified

537772 Cluster profile could not be found please check your configuration Error is blocking Purchase Order Receiving

If you share them in a new blog post as examples that will be a great discussion point


So in this post, I'd like to share 2 things:

1. Generally speaking, we at Microsoft, or rather, more specifically, we in the Warehouse management team, add one or more tests for every single customer reported issue. 

This obviously adds a cost to each bug fix we do, but with this we get a much more reliable release schedule and on-going confidence in our product offering, and have very few regressions despite heavy code churn.


The point I want to make here is - if you are an ISV, and you have not covered your solution with test automation, you should do it now, and reap the benefits. 

If you are a partner getting paid by the hour doing a specific customer implementation, it gets more tricky. However, I believe (I know) it is in your and your customer's interest to justify the cost of adding test automation.

Very frequently today, even with some of our larger customers, we hear test cycles of 1-2 months before taking a newer standard release. That puts a significant on-going cost on the customer, as well as stress, as we at Microsoft keep pushing everyone to be current. This could have been mostly alleviated with test automation

ATL is a framework which allows to write robust (and reasonably fast) test automation with a low cost investment, all things considered. 


2. We do not ship our test automation library. The code is not written with shipping in mind, so it's not necessarily "Microsoft production quality". The value for customers is also marginal. 

But, to answer the ask from the quote above directly, here are the tests added for the 4 issues listed. I did not include the class/test setup logic, nor did I verify these tests are able to run on your specific dataset (internally we have tests that are both dataset dependent and independent). So take them for what they are - mere samples that prove the point - all of these are rather easy to understand when reading, and are rather easy to write, if you know the scenario you want to automate. For all or most of the standard functionality there are already ATL "wrappers" ready to be used.

  • 533573 Creating a sales order’s work without a pick location is not updating the on-hand figures correctly.

	[SysTestCheckInTest]
    public void soRelease_StopWorkOnLocDirFailureFalse_onHandCorrectWorkCompleted()
    {
		// Given
        const InventQty QtyOnReceiptLocation = 100;
        const InventQty SalesQuantity = 1;

        ttsbegin;

        var locDirFailure = whs.locationDirectiveFailures().sales();
        locDirFailure.LocDirFailWork = NoYes::No;
        locDirFailure.update();

        var locProfile = whs.locationProfiles().receipt();
        locProfile.setLPControlled(false).save();

        var receiptLocation = whs.locations(warehouse).create(locProfile);

        // Delete existing sales pick location directive to ensure no impact to the one created.
        var locDir = AtlEntityWHSLocationDirective::find('24 SO Pick', WHSWorkType::Pick, WHSWorkTransType::Sales, warehouse);
        locDir.delete();

        whs.locationDirectives().salesPick().setLocationRange(pickLocation).moveToTop();

        onhand.adjust().forItem(item).forInventDims([receiptLocation]).addOnHandQty(QtyOnReceiptLocation);

        salesOrder.addLine().setItem(item).setInventDims([warehouse]).setQuantity(SalesQuantity).setAutoReservation().save();
        ttscommit;

		// When
        salesOrder.releaseToWarehouse();

		// Then
        var work = salesOrder.work().singleEntity();

        work.lines().withTypePick().withLocationId('').assertSingle();
		
        invent.trans().query().forItem(item).forReferenceCategory(InventTransType::WHSWork)
			.forReferenceId(work.parmWorkId()).assertCount(0);

		onHand.assertExpectedOnHand(
            onHand.spec().forItem(item).withInventDims([warehouse]).withAvailTotal(QtyOnReceiptLocation - SalesQuantity));

        var soPicking = rf.login().salesOrderPicking()
			.setWork(work)
            .setLocation(receiptLocation);

        invent.trans().query().forItem(item).forReferenceCategory(InventTransType::WHSWork)
            .forReferenceId(work.parmWorkId()).withStatusIssue(StatusIssue::ReservPhysical).assertSingle();
        invent.trans().query().forItem(item).forReferenceCategory(InventTransType::WHSWork)
            .forReferenceId(work.parmWorkId()).withStatusReceipt(StatusReceipt::Ordered).assertSingle();

        soPicking
			.confirmPick()
			.confirmPut()
			.assertWorkCompleted();
    }  
  
  • 535311 Cannot do material consumption from mobile device for shelf life items

      [SysTestCheckInTest,
        SysTestCaseAutomaticNumberSequences,
        SysTestFeatureDependency(classStr(WHSProdMaterialConsumptionJournalBOMBatchExpiryDateFeature), true)]
    public void materialConsumption_BatchIsExpiredBySchedDate_JournalLinesCreated()
    {
        const Qty BomLineQty = 1;
        const Qty ProductionOrderQty = 1;

        ttsbegin;

        // Given

        InventModelGroup fefoGroup = invent.modelGroups().fefoWithExpiryCriteriaBuilder().save().record();
        fefoGroup.PickingListBatchExpirationDateValidationRule = WHSPickingListBatchExpirationDateValidation::ReservationDate;
        fefoGroup.update();

        InventTable rawMaterial = items.whsBatchAboveBuilder()
            .setItemModelGroup(fefoGroup)
            .setPdsShelfLife(PdsShelfLife)
            .create();

        InventBatch validBatch = invent.batches().create(rawMaterial);
        validBatch.setExpiryDate(DateTimeUtil::getToday(DateTimeUtil::getUserPreferredTimeZone()) + PdsShelfLife).save();

        onhand.adjust().forItem(rawMaterial).forInventDims([floor, lp1, validBatch]).addOnHandQty(RMItemQty);

        InventTable finishedGood = items.whsBatchBelowBuilder()
            .setItemModelGroup(fefoGroup)
            .setPdsShelfLife(PdsShelfLife)
            .create();

        AtlEntityBillOfMaterials billOfMaterials = this.createBOMAndBOMVersion(rawMaterial, BomLineQty, finishedGood);
        AtlEntityProductionOrder productionOrder = this.createProductionOrder(finishedGood, billOfMaterials, ProdReservation::None, ProductionOrderQty);
        productionOrder.estimate().execute();
        
        productionOrder.scheduleOperation().setSchedDirection(ProdSchedDirection::ForwardFromSchedDate).setFiniteCapacity(false)
            .setFiniteMaterial(false).setSchedDate(DateTimeUtil::getToday(DateTimeUtil::getUserPreferredTimeZone()) + PdsShelfLife + PdsShelfLife).execute();

        productionOrder.start().setAutoBOMConsumption(BOMAutoConsump::Never)
            .setPostPickingList(false).execute();

        ttscommit;

        AtlWhsWorkExecuteProdMaterialConsumption materialConsumption = this.createMaterialConsumptionWithBatch(productionOrder, floor, rawMaterial, validBatch);
        
        materialConsumption.setQuantityToConsume(ProductionOrderQty);

        // When and Then
        materialConsumption.assertMessageOnClickOk("@WAX:MaterialConsumptionJournalLineCreated");

        materialConsumption.assertMessageOnDone(
            strFmt(
                "@WAX:MaterialConsumptionJournalPosted",
                productionOrder.pickingLists().withJournalType(ProdJournalType::Picklist).singleEntity().parmJournalId()));
    }
  
  • 536899 The container contents report does not show an error if shipment does not have a dropoff address specified (Not using ATL, as this was an old existing test. More than 1 test was added / modified here)

      [SysTestCheckInTest]
    public void preRunValidate()
    {
        // arrange
        InventSiteLogisticsLocation inventSiteLogisticsLocation;
        delete_from inventSiteLogisticsLocation;

        Args args = new Args(formStr(WHSContainerTable));
        FormRun formRun = classfactory.formRunClass(args);
        formRun.init();

        FormDataSource containerTableDS = formRun.dataSource(tableStr(WHSContainerTable));
        containerTableDS.executeQuery();
        containerTableDS.markRecord(containerTable, true);

        args = new Args();
        args.caller(formRun);
        args.record(containerTable);

        WHSContainerContentsControllerTestable controller = new WHSContainerContentsControllerTestable();
        controller.parmArgs(args);

        Query query = new Query(queryStr(WHSContainerContents));
        controller.setRanges(query);

        Map queryContract = new Map(Types::String,Types::Class);
        queryContract.insert('Query',query);

        SrsReportDataContract srsReportDataContract = new SrsReportDataContract();
        srsReportDataContract.parmQueryContracts(queryContract);
        controller.parmReportContract(srsReportDataContract);

        // act
        container validateResults = controller.preRunValidate();

        // assert
        this.assertEquals(validateResults, [SrsReportPreRunState::Error, "@WAX:WHSContainerContentsReportMissingPrimaryAddress"], 'Unexpected value in validateResults.');
    }
  
  • 537772 Cluster profile could not be found please check your configuration Error is blocking Purchase Order Receiving

      [SysTestCheckInTest]
    public void poReceive_QualityOrderAndAssignClusterEnabled_WorkAssignedToCluster()
    {
        const Qty PurchOrderQuantity = 2;
        ttsbegin;
        // Given
        whs.warehouses().ensureQualityManagementEnabled(warehouse);
        invent.qualityOrders().ensureCanCreate();
        whs.locationDirectives().qualityItemSampling(whs.locations(warehouse).stage().wMSLocationId, warehouse);
        whs.workTemplates().qualityItemSampling();
        invent.qualityAssociations().defaultBuilder()
            .setOrderType(InventTestReferenceType::Purch)
            .setApplicableWarehouseType(WHSApplicableWarehouseType::QualityManagementOnlyEnabled)
            .setItemRelation(whsItem.ItemId)
            .setSiteId(warehouse.InventSiteId)
            .setDocumentType(InventTestDocumentStatus::Registration)
            .setShowInfo(NoYes::Yes)
            .setAcceptableQualityLevel(100)
            .setQualityProcessingPolicy(WHSQualityProcessingPolicy::CreateQualityOrder)
            .setItemSampling(invent.itemSamplings().onePiece())
            .getResult();
        clusterProfile = whs.clusterProfiles()
            .putawayClusterManualAssignAtReceipt(WHSWorkTransType::Purch, whs.workTemplates().purchaseReceipt().parmWorkTemplateCode());
        ttscommit;
            
        this.addPurchaseOrderLine(whsItem, PurchOrderQuantity);

        // When
        var poItemRecv = rf.login().purchaseOrderItemReceiving()
            .setPurchaseOrder(purchOrder)
            .setItem(whsItem)
            .setQuantity(PurchOrderQuantity);

        AtlQueryWHSWork workQuery = purchOrder.work().withClusterProfileId(clusterProfile.parmProfileId());
                
        // Then
        poItemRecv.assertErrorOnConfirmUnitSelectionWithWorkQuery(workQuery, clusterProfile.parmProfileId(), 'Wrong message');
        invent.qualityOrders().query().forPurchaseOrder(purchOrder.record()).assertCount(1, 'Should have 1 quality order');
    }
  


Hope this helps. Do reach out to us with feedback / suggestions.

Do make use of CDE (Community Driven Engineering) to submit additions / bug fixes. We do expect ATL tests with those fixes.

Tuesday, April 13, 2021

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

 Introduction

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!

Thanks

Sunday, March 14, 2021

[Tutorial] Work pick line overview

Introduction

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.

Configuration

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 docs.microsoft.com:

  • 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

Demo

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:



Conclusion

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!