Controlling reservations for Warehouse Management enabled items (WHS) – Part 2

In this blog post we are going to go into details about how you can gain more control of your reservations by introducing a new reservation strategy.

If the reservation of warehouse management enabled items is not fresh in your memory, take a look at Controlling reservations for warehouse management enabled items (WHS) – Part 1.

The code snippets are written for Microsoft Dynamics AX 2012 R3 CU 11, but can be easily ported to the to the latest version of Dynamics 365 for Operations

The code in this blog is provided “as-is”. You bear the risk of using it.

The scenario

We are going to imagine that a company is selling an item in different qualities and is using the inventory status to reflect the quality.

Normally the company sells whatever qualities are available, but for a few special customers, the quality needs to be selected during order taking, which means the sales order taker needs to able to select the inventory status.

To have as much stock available for customers that request a specific quality the company wants to postpone the decision about which qualities are used for the orders were no special quality is requested.

The challenge is to postpone the reservation of specific qualities (inventory statuses) but still allow some orders to select specific qualities.

In order to create work all dimensions above the location must be specified on the load lines. Therefore the decision about what qualities to ship can only be postponed until the point in time where the load is released to the warehouse.

To achieve this, we are going to introduce a new reservation strategy. The purpose of this strategy is to ensure that we reserve all the dimensions above the Inventory status but not the inventory status itself.

The new reservation strategy

Our new strategy called AllAboveStatus will be added to the hierarchy by extending the WHSReservationHierarchyLevelStrategy class.

The instantiation of the class is done using the SysExtension framework (if you want to read more about SysExtension framework it is well covered here: The Microsoft Dynamics AX 2012 extension framework – Part 1.

 

All we need to do is to add a new value to the WHSReservationHierarchyLevelStrategyType enum and decorate our class with an attribute:

[WHSReserveHierarchyLvlStratAttribute(WHSReservationHierarchyLevelStrategyType::AllAboveStatus)]
class WhsReservationLevelStrategyAllAboveStatu extends WHSReservationHierarchyLevelStrategy

Important methods

There are a few methods we need to override. Here I am including the two complex ones.

public List getReservableHierarchyListTopDown()
{
    if (!reservationHierarchyListTopDown)
    {
        /* This should be on the cache but is kept here for illustration purposes */

        reservationHierarchyListTopDown = WHSReservationHierarchyListBuilder::construct().buildPartialHierarchyAbove(
                    inventTable.whsReservationHierarchy(),
                    WHSReservationHierarchySortOrder::TopDown,
                    fieldNum(InventDim, InventStatusId),
                    false);

    }

    return reservationHierarchyListTopDown;
}

 

The method must return a list that contains all the fields from the reservation hierarchy above the Inventory status.

public WHSReservationHierarchyLevel getReservationHierarchyLevel()
{
    return this.reservationHierarchyProvider().getDimLevel(inventTable, fieldNum(InventDim, InventStatusId)) - 1;
}

 

The method must return the level that we want to reserve against.

That takes care of introducing the new strategy.

Determining when to use the new strategy

Now we need a way to determine when the new strategy should be used.

We are going to keep it simple so this is done by adding a new No/Yes field to the SalesLine table. If the field is set, we will use the new strategy.

We just need to override the reservationHierarchyLevelStrategyList method on the InventMov_Sales class .

 

public List reservationHierarchyLevelStrategyList(InventDim _inventDimReservationCriteria)
{
    List ret;

    ret = super(_inventDimReservationCriteria);
    
    if (SalesOrderLine.WHSUseNonStatusSpecificReservation //new field
    &&  (this.canHaveReservedWork()))
    {
        // put our new strategy at the start of the list so it is picked first
        ret.addStart(WHSReservationHierarchyLevelStrategy::newFromStrategyType(WHSReservationHierarchyLevelStrategyType::AllAboveStatus, this.inventTable(),_inventDimReservationCriteria));
    }
    
    return ret;
}

 

Uptake the strategy in the Reservation page

To be able to use our new strategy in the Reservation page we need to make one more minor change to WHSReservationHierarchyLevelStrategy::newPrimaryStrategyFromMovement which controls what strategy is used for the Reservation page.

 

public static WHSReservationHierarchyLevelStrategy newPrimaryStrategyFromMovement(
    InventMovement  _movement,
    InventDim       _inventDimReservationCriteria,
    boolean         _isPhysicalReservation)
{
    WHSReservationHierarchyLevelStrategyType    whsReservationHierarchyLevelStrategyType;
    container                                   strategyTypes;
    WHSReservationHierarchyLevelStrategy       WHSReservationHierarchyLevelStrategy;

    ListEnumerator le = _movement.reservationHierarchyLevelStrategyList(_inventDimReservationCriteria).getEnumerator();

    // take the first one in the list since that should be the primary one
    if (Le.moveNext())
    {
        return le.current();
    }
    else // keep the old code in case some movements return an empty list
    {
        strategyTypes = WHSReservationHierarchyLevelStrategy::determineStrategyTypesFromMovement(_movement,_inventDimReservationCriteria, _isPhysicalReservation);

        whsReservationHierarchyLevelStrategyType = conPeek(strategyTypes,1);

        WHSReservationHierarchyLevelStrategy =  WHSReservationHierarchyLevelStrategy::newFromStrategyType(whsReservationHierarchyLevelStrategyType,_movement.inventTable(),_inventDimReservationCriteria, _movement.inventDimGroupSetup());
        

    }
    return WHSReservationHierarchyLevelStrategy;
}

 

Seeing it in action

Note: You should ensure that inventory status is not defaulted on your sales lines. This is controlled in the Warehouse management parameters: the Use default status for sales orders and transfer orders check box should be cleared:

DefaultInventoryStatus

 

When the new field is enabled on the sales line, the reservations looks like this:

ReservationForm

When the reservation is done, it does not include the inventory status:

Changing the reservation so we can create work

If we have used the new strategy the Inventory status is missing on our reservations. Since it is a requirement that all dimensions above location are specified before we create work, we have a challenge.

So before we can create work we need to ensure that the Inventory status and all other dimensions above location are included in the reservation so it can be synchronized to the load lines.

We can choose different approaches for updating the reservations.

  1. Re-reserve using a different reservation strategy so the reservation system determines the missing dimensions.
  2. Update the dimensions using the InventUpd_ChangeDimension class . This option could be used if we wanted to change the reservations to some specific dimensions that we know.

For simplicity, we are going to use option 1. The code below illustrates how to change reservations for a single sales order.

public static server void main(Args _args)
{
    SalesId                 salesId = 'SO-101284'; //order we want to re-reserve
    SalesLine               salesLine;
    InventUpd_Reservation   reservation;
    InventQty               reserveQty;
    List                    strategyList;  
    InventMovement          movement;
    
    ttsBegin;
    // go through the lines that used the new strategy
    while select salesLine 
        where SalesLine.salesId == salesId
        &&    SalesLine.WHSUseNonStatusSpecificReservation == NoYes::Yes
    {
        //whatever was reserved before will be re-reserved
        reserveQty = salesLine.reservedPhysical();
        
        //Un-Reserve the entire reserved quantity
        reservation = InventUpd_Reservation::newMovement(
                                                InventMovement::construct(salesLine),
                                                reserveQty,
                                                true);        

        reservation.parmAllowAutoReserveDim(false);//don't give the un-reserved quantity to other ReservOrdered transactions
        reservation.updateNow();
        
        movement = InventMovement::construct(salesLine);
        reservation = InventUpd_Reservation::newMovement(
                                                movement,
                                                -reserveQty,
                                                false);
        
        // Illustrating how to inject a list of strategies to the reservation 
        strategyList = new List(Types::Class);
        strategyList.addEnd(WHSReservationHierarchyLevelStrategy::newFromStrategyType(WHSReservationHierarchyLevelStrategyType::AboveLocation, movement.inventTable(), movement.inventdim()));
        reservation.setWHSReservationHierarchyStrategyList(strategyList);
        // we could also take control of the queries used to find on-hand, for example adding some ordering on Status using : 
        //reservation.setWHSInventReserveQueryBuilder(...);
        
        //now we are reserving with all dimensions above location
        reservation.updateNow();
    }
    
    ttsCommit;
}

 

After running the code the reservations for the sales line now includes inventory status:

inventtransreservation_WithStatus

Wrapping up

In this blog I showed how you can introduce a new reservation strategy that allows you to postpone decisions about which dimensions to reserve against.

If you wanted to start with reservations on a site level and then later distribute them to different warehouses you could follow a similar approach.

You could also replace Inventory status with Batch ID to achieve similar behavior for items that are batch controlled and have batch above location.

This would allow you to reserve the actual batches just before release. (What I have described here won’t work for batch below location items – that is a subject for a different blog post).

I hope this has given you some insight into some of the flexibility that the reservation system gives you.

Related
Recommended