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.
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 😦
}
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
LikeLike
Thanks Arun for reading my blog. I don’t perform consultancy here, but what you are looking for is basically just a join between sum() InventSum, InventDim and WMSLocation. But there are other aspects that also needs to be taken into considderation. I suggest that you address your questions to the Dynamics AX forums that we have in the community. Happy DAX’ing
LikeLike
Hi Kurt,
I have resolved my issue and also posted on my blog :
https://arungarg1987.wordpress.com/2015/09/23/filter-invent-on-hand-based-on-wmslocation/
🙂
LikeLike
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.
LikeLike
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.
LikeLike
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 🙂
LikeLike
Great Kurt, your PDA solution seems to be very user-friendly which FKNR will benefit from! 🙂
LikeLike