Code highlighting

Monday, January 17, 2022

[Tutorial] Warehouse management app detours

Feature overview

Warehouse management app detours is a feature we have shipped recently, available as part of 10.0.23, which enables a warehouse worker to park a task they are currently executing, perform another mobile device operation, and then come back to exactly where they left off, once the detour is complete. 

This is feedback we have heard way too often from workers in the warehouse, saying they are afraid to navigate away from their current step for risk of losing their progress, the lock on the work they were performing, etc. 

The detours help reduce that fear, and, what is great about the feature, is that pretty much any mobile device menu item can serve as a detour in another mobile app flow.

Enable feature

Feature management

In order to enable the detours feature, you first need to enable the "Warehouse app step instructions" feature, both shown in the image above. This is what really drives the configuration options for detours, so that you can be flexible in where the various detours show up in a mobile flow.

In addition to enabling the feature, you should also make sure to regenerate the default setup / update the Warehouse app field names configuration, as we've significantly expanded the metadata associated with the fields (aka steps in a flow).

Configuring mobile device steps

You configure detours through the Mobile device steps screen. 

Mobile device steps

First, you need to identify the step in the flow where you would like to show a new detour menu item, and then add configuration for this step for a selected mobile device menu item.

In my examples today, I have decided to show detours on the "Scan a license plate" step as part of the Sales picking flow, as well as in the Item Inquiry screen from each of the cards. 

Adding specific step configuration allows to configure an alternative set of instructions for the user, in case they should be different from the default for the selected menu item, as well as choose (if you enable the corresponding feature) the information that should be prominently shown to the user in the details section, so they don't need to click into the details to view it. And, which is what we are going to do now, it allows to add one or more detours to this step. 

Detour configuration for Scan a License Plate step of Sales picking flow

Detour configuration for Item inquiry list screen

Above is how I have configured detours for Sales picking and Item inquiry flows.

Note

It's not just about invoking another flow. You can actually also pass information back and forth between the main flow and the detour. In the examples above, I'm passing in the Location from the main flow into the detours, and in the Location inquiry detour, I'm also passing back the license plate. Which allows me to select the license plate from a list instead of entering it manually, if I for some reason is not able to scan the value in at the location.


The options with detours are almost limitless, especially when you take custom flows into consideration. Some of the examples could be allowing to do an ad hoc movement of inventory from the receiving flow, or even from a picking flow, so that the worker can clean up the aisle to get past some block. You could do ad hoc replenishments, or perform counting on the spot, if you notice something is off while picking goods. I'd love to hear what you come up with in your warehouse!

Sales picking with location inquiry detour demo

To demonstrate how detours behave on the mobile device, I've created a simple sales order picking work order with 1 line, picking 10 pcs of A0001 from FL-001 location, which is license plate controlled.

After scanning in the work ID created above, we arrive at the step, where the worker needs to specify the license plate to pick from in location FL-001. 

Detour menu items visible for Scan a license plate step

You can clearly see which menu items are detours because of the additional icon (return arrow) shown above the main icon. This signals to the worker that when they are done with the detour, they will be returned to their current step in the current flow.

If we select Location inquiry, we are directed to the first step in the location inquiry flow, and you can see that Location is already pre-populated for us with the value coming from the sales picking flow. I can of course go and change the value to another location if needed.

First step in Location inquiry detour

After I confirm the location, I am presented with the list of license plates and products in the selected location. 

Location inquiry detour - list

Now, because I am in a detour, I can actually select one of the cards displayed, and when clicking back, the selected context is carried back to the main flow, including not only the card header (in this case, Item number), but all/most of the other related information. In this case, I'm interested in the license plate, which is how I have configured the detour above.

Note, also, that when selecting to go back, the first step (for scanning the location) is "skipped", and we go out all the way back to the main flow. 

License plate populated from detour

As you can see, the license plate was transferred back and I can simply go ahead and confirm the entry to proceed. Nice!

Item inquiry flow with spot counting detour demo

Now, I'd like to go into an item inquiry flow and view the on hand available for A0001. You can see I have four locations where this product is available. And let's say I actually want to re-count this location, as upon visual inspection something seems off (or maybe I noticed something fell and broke, for example). That is now possible with detours. 

All I need to do is long-press the card for the specific location I would like to count, and a list of available actions will pop up.

Detours available for BULK-001 card

Selecting one of the detours will pass the current card context into the detour, thus when the spot counting opens, for example, the location to count will already be pre-populated, as shown below:

Spot Counting detour with pre-populated value and step instructions

Note that the step instructions are also displayed, as this is the first time I open this particular flow. I can dismiss the instructions until next time, or for good by selecting the "Don't show again" check box.

In summary, not only do you have detours available in your typical page by page flows, but also on inquiry screens like location or item inquiry, providing even more flexibility.

And, I guess, it makes sense to explicitly call out that both ProcessGuide-based flows as well as WHSWorkExecuteDisplay-based flows are supported.

Want your feedback

Currently, when a detour is opened, we do not automatically confirm the value, even if it's populated based on the main flow. One of the reasons we discussed was that workers might get confused, expecting a particular and familiar step, but instead getting something else. Another is that it would be slower, as we'd still be going step by step on the backend, so the worker might perceive a longer wait time for the detour screen.

I'd like to ask for feedback on this!
  • What detours do you find relevant for your business? 
  • Do workers find it convenient?
  • Is an auto-submit feature necessary?
Leave comments here, in the below Youtube video, or reach out directly!

YouTube video



Thursday, November 25, 2021

[We're hiring!] Multiple open positions for passionate and talented software engineers in Microsoft Cloud for Sustainability team in Atlanta, GA

Hi everyone

As you might have read on Yammer or other social media, I have decided to take a step away from Finance and Operations and Warehouse management, and a few months back accepted to lead the Sustainability engineering team in Atlanta, Georgia. The team is working on  2 new exciting and relevant products, Microsoft Cloud for Sustainability, as well as the Microsoft Emissions Impact DashboardThe latter is now generally available, while the former has just recently been released as a trial (public preview)

As we work towards GA of Microsoft Cloud for Sustainability early next year, we are looking for more engineers to join our team. 

Positions at different levels of experience (mid-level to senior) are available, and you can apply through the following link:

https://industryuseng-ms.icims.com/jobs/1213955/senior-sde/job?mode=view


So if you meet the requirements and like what you read in the press releases about the Cloud for Sustainability product vision, and want to support Microsoft's path to a carbon negative future, please apply for the job, and/or reach out here or on LinkedIn.


Really looking forward to your application!

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.