Search for available putaway location using X++

When receiving products from purchase, production or any other receipt, then most companies wants the system to present a location where the items should be placed.

Dynamics AX 2012 have the capability to find a available location in the warehouse, but this algorithm is based on that the receipt do have a pallet.

If you want to examine the standard X++ code for this, then take a look at Table\WMSPallet.findFreeLocation(..), and just move forward in the code. 

But we who have been working with logistics for many years, know that it is just a fraction of our customers, that are using pallets. Most just have pure SKU’s that they want to store in their warehouse.

So in the PDA, I implemented a way to suggest put-away locations to the warehouse operator, that works without pallets.

image

The algorithm is simpler than the standard DAX2012 search for pallet location, but the response I get from my customers, it that it is more practical.  So what the code basically do is :

1. If the item/warehouse have a default receipt location, then use this.

2. If the item have a default picking location, then suggest this.

3. Search for a existsing buffer location with onhand, and then suggest this.

4. Seach for a buffer location where the item has been before, but now is empty, then suggest this.

5. Search for any empty and available buffer location.

Here is the code available for the Dynamcis AX community. (it’s not perfect, but ment as an inspiration for all DAX developers that every day is creating value for our customers)


WMSLocationId suggestedWmsLocationId(ItemId _itemId, InventDimId _inventDimId)
{
    InventItemLocation inventItemLocation;
    WMSStoreZoneArea   WMSStoreZoneArea;
    InventDim          parmInventdim = InventDim::find(_inventDimId);
    InventDim          inventDim;
    InventDim          inventDimCriteria;
    InventDimParm      inventDimParm;
    InventSum          inventSum;

    InventSum          inventSum2;
    InventDim          inventDim2;
    WMSLocation        wmsLocation;


    if (parmInventdim.wmsLocation() )
         return parmInventdim.wmsLocationId;

    inventItemLocation = InventItemLocation::findFixedWarehouse(_itemId, _inventDimId);
    if (inventItemLocation)
    {
        //1. use default receipt location on item
        if (inventItemLocation.wmsLocationIdDefaultReceipt)
            return inventItemLocation.wmsLocationIdDefaultReceipt;

        //2. use picking location on item
        if (inventItemLocation.wmsPickingLocation)
            return inventItemLocation.wmsPickingLocation;
    }

    //3. Search for existing buffer locations with item onhand
    inventDimCriteria = parmInventdim;
    inventDimParm.initFromInventDim(inventDimCriteria);
    inventDimParm.wmsLocationIdFlag = false;
    inventDimParm.wmsPalletIdFlag = false;
    inventDimParm.InventBatchIdFlag = false;
    inventDimParm.InventSerialIdFlag = false;

    while select firstonly sum(PhysicalInvent) from inventSum
            group by InventDimId
            where   inventSum.ItemId == _itemId
                &&  inventSum.PhysicalInvent > 0
            #InventDimExistsJoin(InventSum.inventdimid,inventdim,inventDimCriteria, InventDimParm)
            exists join wmsLocation
                where   wmsLocation.InventLocationId        == inventDim.InventLocationId
                    &&  wmsLocation.wmsLocationId           == inventDim.wmsLocationId
                    &&  !wmsLocation.InputBlockingCauseId
                    && (wmsLocation.LocationType            == WMSLocationType::Buffer ||
                        wmsLocation.LocationType            == WMSLocationType::Pick)
    {
        if (inventItemLocation.wMSStoreZoneId) 
            {
                select firstonly WMSStoreZoneArea 
                    where   WMSStoreZoneArea.storeZoneId == inventItemLocation.wMSStoreZoneId
                        &&  WMSStoreZoneArea.storeAreaId == inventSum.inventDim().wmsLocation().storeAreaId;
            
                if (WMSStoreZoneArea)
                    return wmsLocation.wMSLocationId;   
            }
        else
            return inventSum.inventDim().wmsLocationId;
    }
    //4. Search for existing buffer locations where the item has been before, is zero, and no other items
    while select firstonly sum(PhysicalInvent) from inventSum
            group by InventDimId
            where   inventSum.ItemId == _itemId
                &&  inventSum.PhysicalInvent == 0
            #InventDimExistsJoin(InventSum.inventdimid,inventdim,inventDimCriteria, InventDimParm)
            exists join wmsLocation
                where   wmsLocation.InventLocationId        == inventDim.InventLocationId
                    &&  wmsLocation.wmsLocationId           == inventDim.wmsLocationId
                    &&  !wmsLocation.InputBlockingCauseId
                    && (wmsLocation.LocationType            == WMSLocationType::Buffer ||
                        wmsLocation.LocationType            == WMSLocationType::Pick)
    {
        select firstonly sum(PhysicalInvent) from inventSum2
            where   inventSum2.ItemId != _itemId
                &&  inventSum2.PhysicalInvent > 0
                exists join inventDim2
                    where   inventDim2.InventLocationId  == inventSum.inventDim().InventLocationId
                        &&  inventDim2.wmsLocationId     == inventSum.inventDim().wmsLocationId;
        
        if (inventSum2.PhysicalInvent == 0)
        {    
            if (inventItemLocation.wMSStoreZoneId) 
            {
                select firstonly WMSStoreZoneArea 
                    where   WMSStoreZoneArea.storeZoneId == inventItemLocation.wMSStoreZoneId
                        &&  WMSStoreZoneArea.storeAreaId == inventSum.inventDim().wmsLocation().storeAreaId;
            
                if (WMSStoreZoneArea)
                    return wmsLocation.wMSLocationId;   
            }
            else
                return inventSum.inventDim().wMSLocationId;
        }   
    }
    //5. Search for empty and available location in buffer
    while select wmsLocation order by SortCode, wmsLocationId
        where   wmsLocation.InventLocationId == parmInventdim.InventLocationId
            &&  !wmsLocation.InputBlockingCauseId
            && (wmsLocation.LocationType           == WMSLocationType::Buffer ||
                wmsLocation.LocationType           == WMSLocationType::Pick)
        exists join inventDim
            where   inventDim.InventLocationId  == wmsLocation.InventLocationId
                &&  inventDim.wmsLocationId     == wmsLocation.wmsLocationId
        exists join inventSum
            where   inventSum.InventDimId    == inventDim.InventDimId
                &&  inventSum.PhysicalInvent == 0
                &&  inventSum.Ordered        == 0
    {
        select firstonly sum(PhysicalInvent) from inventSum2
            where   inventSum2.ItemId != _itemId
                &&  inventSum2.PhysicalInvent > 0
                exists join inventDim2
                    where   inventDim2.InventLocationId  == wmsLocation.InventLocationId
                        &&  inventDim2.wmsLocationId     == wmsLocation.wmsLocationId;

        if (inventSum2.PhysicalInvent == 0)
        {    
            if (inventItemLocation.wMSStoreZoneId) 
            {
                select firstonly WMSStoreZoneArea 
                    where   WMSStoreZoneArea.storeZoneId == inventItemLocation.wMSStoreZoneId
                        &&  WMSStoreZoneArea.storeAreaId == wmsLocation.storeAreaId;
            
                if (WMSStoreZoneArea)
                    return wmsLocation.wMSLocationId;   
            }
            else
                return wmsLocation.wmsLocationId;
        }    
    }
    return ''; //No locations was found 😦
}

7 thoughts on “Search for available putaway location using X++

  1. HI Kurt,

    How we can filter data of inventsum(Invent on-hand) based on WMSLocation data.

    For example.

    I have a WMSLocation tabel with wmsLocationId and inventLocationId field value and I want to filter InventSum table data based on these values. So,how i can do these filteration.

    i.e
    For each location there should be filtered Invent On-hand.

    Regards
    Arun

    Like

  2. Hi Kurt,

    Good stuff in this article.

    I was wondering whether you have ever come across the need to handle replenishment from bulk to pick location that is not based on pallet transports being generated automatically. The warehouse manager will in most cases want AX2012 to suggest a transfer from bulk to pick if stock level becomes lower than the required minimum setup in Warehouse items, but since the current functionality is based on pallets it doesn’t seem possible to create an inventory transfer journal automatically with the suggested item transfer from bulk to pick.

    Like

    • Hi Geert. I know exacly what you are refering to, and I have several customer where they do refilling tasks of pick area from bulk, without having pallets. Personally I prefer to do wave planning, and not relying to heavily on minimum location quantity in the picking locations. The reason for this, is to only refill when needed, and therefore using the warehouse worker resources as efficient as possible. And only refilling for those orders that should/can be shipped.

      A customer (Spar Kjøp) have at any time more than 14.000 SKU’s on sale, and making sure that all is available in the picking locations was solved using the To-Increase SCS/WM&D solution. Take a look at Spar Kjøp on YouTube and you see what I’m talking about.

      Like

      • Hi Kurt,

        Thanks for swift and valuable reply. If you only refill when needed it seems you will increase the total amount of time for picking since you would first have to refill from bulk to pick and then have the picker perform the actual pick. Timewise, is there any measurable advantage in this procedure? Do you handle all refill in the morning before picking begins? I assume you generate transfer orders on the basis of requested items in case the pick location does not have sufficient onhand stock to fulfill the requirement, is that correct?

        I looked at both videos and although it is obvious that the solutions from To-Increase seem to be doing their job I am not quite sure how item arrival, put away and cross-docking is handled in practical terms.

        Thanks for a very good blog Kurt, they are hard to find 🙂

        Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.