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:
When the new field is enabled on the sales line, the reservations looks like this:
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.
- Re-reserve using a different reservation strategy so the reservation system determines the missing dimensions.
- 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:
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.