I recently got asked to create a generic solution to optimize the picking routes generated by D365 FOE – We have a customer that have two categories of items, heavy items and light items.
They want to place the heavy items at the bottom of the pallets (for obvious reasons), and they can manage this standard using grouping on the work template (create a sort in “Edit query”, and mark the field as “Group by this field” under work header breaks). The issues is that, when they are finished picking all the heavy items, the system dictates that they must start at the lowest sorting code again (eg. they must start picking at the beginning of the aisle again). This is essentially a waste of time, since they could simply pick the light items on the way back to the packing stations.
Instead of trying to customize the way D365 is sorting work (if at all possible), I wanted to create my own routine that optimizes pick work lines after they are created, using a set of configurable queries. My decision settled on a new wave step.
It is simple to create new wave steps. All you have to do is to create a new class that extends from “WHSCustomWaveStepMethod”, implement all abstract methods, and decorate it with the “WHSWaveTemplateTypeFactoryAttribute” attribute.
This also allows the solution to only affect certain wave templates and can easily be deactivated if the code causes issues.
I’d like to give special thanks to Blue Horseshoe. I read about this on their blog some time ago, but I cannot find the post anymore.
[WHSWaveTemplateTypeFactoryAttribute(WHSWaveTemplateType::Shipping)]
class WHSWavePostOptimizerWaveStepMethodDax extends WHSCustomWaveStepMethod
{
public boolean process(WhsPostEngine _whsPostEngine)
{
WHSWorkTable whsWorkTable;
WHSWaveTable whsWaveTable = _whsPostEngine.parmWaveTable();
boolean ret = true;
while select whsWorkTable
where whsWorkTable.WaveId == whsWaveTable.WaveId
&& whsWorkTable.WorkStatus == WHSWorkStatus::Open
{
ret = ret && this.processWork(whsWorkTable);
}
return ret;
}
private boolean processWork(WHSWorkTable whsWorkTable)
{
#OCCRETRYCOUNT
boolean ret;
try
{
ttsbegin;
...Insert logic here...
ttscommit;
ret = true;
}
catch (Exception::Deadlock)
{
// retry on deadlock
retry;
}
catch (Exception::UpdateConflict)
{
// try to resolve update conflict
if (appl.ttsLevel() == 0)
{
if (xSession::currentRetryCount() >= #RetryNum)
{
throw Exception::UpdateConflictNotRecovered;
}
else
{
retry;
}
}
else
{
throw Exception::UpdateConflict;
}
}
catch(Exception::DuplicateKeyException)
{
// retry in case of an duplicate key conflict
if (appl.ttsLevel() == 0)
{
if (xSession::currentRetryCount() >= #RetryNum)
{
throw Exception::DuplicateKeyExceptionNotRecovered;
}
else
{
retry;
}
}
else
{
throw Exception::DuplicateKeyException;
}
}
return ret;
}
public Name displayName()
{
return "Wave post optimizer";
}
}
Under wave processing methods, click the “Regenerate methods” button. D365 should pick up your new method
You can now add it to your wave template.
And thats it! Can it get any easier than this?