Automating Fulfillment Workbench - Erp.OrderAlloc.GetLotBinOnHand() Error - "You need to select one demand record."

Hello all,

I am trying to do a little bit of automation of fulfillment workbench. I have started an Epicor Function that performs an “inventory transfer” and “allocation” for pieces in a given lot. For now, I am concentrating on implementing only the “allocation” step. Here’s roughly what I’m trying to achieve:

For a given qty of a given lot:

  1. AllocateByLotBin() such that either all orders are filled or the lot is fully allocated.
  2. AutoAllocate() to dip into inventory to fill orders if needed/possible

So far I have identified most of the chain via traces, but I’m stumped at an error that’s occurring when I call Erp.OrderAlloc.GetLotBinOnHand().

Error Details:

{
      "Message": "You need to select one demand record.",
      "Type": "Error",
      "Program": "Erp.Services.BO.OrderAlloc.dll",
      "Method": "GetLotBinOnHand",
      "ColumnNumber": 17,
      "LineNumber": 5511
}

BO Methods:

  1. Erp.OrderAlloc.GetListOfOrders()
  2. Erp.OrderAlloc.OrderAllocationGetRows()
  3. Erp.OrderAlloc.Recalculate()
  4. Erp.OrderAlloc.CheckDates()
  5. Erp.OrderAlloc.OneDemandType()
  6. Erp.OrderAlloc.GetLotBinOnHand() <---- Error

It seems after OrderAllocationGetRows() I have an OrderAllocListTableset with the expected record in the “OrderAlloc” table. Next, I pass the OrderAllocListTableset to Recalculate() which appears to clear the “OrderAlloc” table and add to the “OrderAllocSupply” table. The problem now is that I need to pass the OrderAllocListTableset to GetLotBinOnHand() in order to return the available lots, but there is no “select” field on the “OrderAllocSupply” table for me to indicate which “demands” to select and satisfy the error.

Any ideas?


Background: We have developed a custom application used in the last operation of one of our processes. We’ve already integrated an Epicor Function that automatically creates the labor transactions at the end of the operation which results in the lot being transferred into inventory. The next step is to put the product away in an inventory bin. We used to place the inventory at a designated bin location until a sales manager allocated the material to an order via fulfillment workbench, prompting a picker to pick the pieces. However, “almost” everything we do is “build-to-order” so at the time the material is being placed in inventory, the demand already exists and we know what order it should be going to. Thus, if we were able to “allocate” and “transfer” the inventory at the end of the last operation, we could direct the “placer” to deposit the material in a staging bin corresponding to the order.

Reference to a similar posts:

Figured it out, but my goodness was it difficult to track down…

Turns out you need to set “OrderAllocTableset.OrderAlloc.SelectedForAction” to “true” AND set the Row Mod to “U” for GetLotBinOnHand to recognize that the demand is selected. I am vaguely aware of what Row Mod does so I guess that makes sense. I had the first part figured out for a while, and after comparing lots of traces I was able to deduce the Row Mod part. However, then it was not obvious how to achieve that in a Widget function.

Unfortunately, the “Set Field” widget does not automatically (or provide the option to) set the Row Mod to “U” (RowMod is hidden from the column selections for some reason?). I would think this is a fairly common requirement?

This post gives a work-around for Row Mod using Widgets (using “Update Table by Query”):

2 Likes

Hi Mike,

I’ve tried to do this (but just in code rather than widgets - spent 3 days on it already!) - would you mind sharing the BPM?
Thanks

Mark

@gunny72 I ended up working in code as well. here is something to get you started. Note that this is an Epicor Function, not a BPM. There are may be small adjustments for a BPM. You could also create an Epicor Function and then call from BPM, as I do.

AllocateByLot

this.CallService<OrderAllocSvcContract>(orderAllocBO => {

    // get demands for given part
    OrderAllocTableset myOrderAllocTableset = this.EfxLib.InventoryAutomationLib.GenerateOrderAllocTablesetByPart(this.PartNum);
    int lineCount = myOrderAllocTableset.OrderAlloc.Count();
    
    // select the largest demand in the next month
    OrderAllocRow myOrderAllocRow = myOrderAllocTableset.OrderAlloc.First(); // add a Where() to select which demand
    myOrderAllocRow.SelectedForAction = true;
    myOrderAllocRow.ReleaseCount = lineCount; //should be the count of records in myOrderAllocTableset.OrderAlloc (I have inferred this from traces and it seems to work, doesn't make much sense to me tho. without, it fails)
    myOrderAllocRow.LineCount = lineCount;
    myOrderAllocRow.RowMod = "U";

    // get the on hand quantities
    string cMessageText = "";
    string oErrorText = "";
    string oDemandtype = "";
    bool lReleased = false;
    (myOrderAllocTableset, cMessageText, oErrorText, oDemandtype) = this.EfxLib.InventoryAutomationLib.GetOnHandQtys(myOrderAllocTableset); // get the current on hand quanitites

    // find the lot with matching LotNum Part 
    PartAllocLotRow myPartAllocLotRow = myOrderAllocTableset.PartAllocLot.First(); // add a Where() to select which lot
    int allocQty = 10; // put some calculation here
    
    // select the lot, allocate by lot
    myPartAllocLotRow.Allocate = true;
    myPartAllocLotRow.NewAllocatedQty = allocQty;
    
    orderAllocBO.AllocateByLotBin(ref myOrderAllocTableset, "", "All", out cMessageText, out lReleased); // perform the allocation
    orderAllocBO.GetFWBLimitedRefresh(out bool opFWBLimitedRefresh);
    orderAllocBO.GetFWBLimitedRefresh(out opFWBLimitedRefresh);
    libDeferredUpdate.UpdPQDemand(); // must process the deferred updates or the tables get de-synchronized (i think this code is Epicor Function specific, search the forum for the BPM variant)

});

GenerateOrderAllocTablesetByPart


this.CallService<OrderAllocSvcContract>(orderAllocBO => {
  this.CallService<PartAllocTemplateSvcContract>(partAllocTemplateBO => {
    OrderAllocListTableset orderAllocListTableset = orderAllocBO.GetListOfOrders("","","",$"OrderRel.PartNum = '{ PartNum }'","","","","","false","OrderHed.ReservePriorityCode , OrderRel.ReqDate","","",0,0, out bool morePages, "");
    this.orderAllocTableset = orderAllocBO.OrderAllocationGetRows(orderAllocListTableset, 0);
    PartAllocTemplateTableset partAllocTemplateTableset = partAllocTemplateBO.GetByID("YouTemplate");
    
    PartAllocTemplateRow partAllocTemplateRow = partAllocTemplateTableset.PartAllocTemplate.First();
    
    // Update orderAllocTableset.PartAllocTrans with template
    foreach (PartAllocTranRow partAllocTranRow in orderAllocTableset.PartAllocTran)
    {
      partAllocTranRow.AddHoc = false;
      partAllocTranRow.WarehouseCode = partAllocTemplateRow.WarehouseCode;
      partAllocTranRow.AllocTemplateID = partAllocTemplateRow.AllocTemplateID;
      partAllocTranRow.AllocTempDescAllocTemplateDesc = partAllocTemplateRow.AllocTemplateDesc;
      partAllocTranRow.RowMod = "U";
    }
        
  });
});

GetOnHandQtys

this.CallService<OrderAllocSvcContract>(orderAllocBO => {
  SlimOrderAllocTableset slimOrderAllocTableset = new SlimOrderAllocTableset();
  
  // transform orderAllocListTableset to SlimOrderAllocTableSet (there's probably a LINQ way to do this)
  foreach (OrderAllocRow orderAllocRow in inputOrderAllocTableset.OrderAlloc)
  {
      SlimOrderAllocRow slimOrderAllocRow = new SlimOrderAllocRow();
       
      slimOrderAllocRow.AssemblySeq = orderAllocRow.AssemblySeq;
      slimOrderAllocRow.Company = orderAllocRow.Company;
      slimOrderAllocRow.DemandType = orderAllocRow.DemandType;
      slimOrderAllocRow.FulfillmentSeq = orderAllocRow.FulfillmentSeq;
      slimOrderAllocRow.JobNum = orderAllocRow.JobNum;
      slimOrderAllocRow.MtlSeq = orderAllocRow.MtlSeq;
      slimOrderAllocRow.OrderLine = orderAllocRow.OrderLine;
      slimOrderAllocRow.OrderNum = orderAllocRow.OrderNum;
      slimOrderAllocRow.OrderRelNum = orderAllocRow.OrderRelNum;
      slimOrderAllocRow.SelectedForAction = orderAllocRow.SelectedForAction;
      slimOrderAllocRow.TFOrdLine = orderAllocRow.TFOrdLine;
      slimOrderAllocRow.TFOrdNum = orderAllocRow.TFOrdNum;
      slimOrderAllocRow.RowMod = "A";
      
      slimOrderAllocTableset.SlimOrderAlloc.Add(slimOrderAllocRow);
      
  }
  orderAllocBO.CheckDates(ref slimOrderAllocTableset, out string cMessageText);
  orderAllocBO.OneDemandType(ref inputOrderAllocTableset, out string oErrorText, out string oDemandType);
  orderAllocBO.GetLotBinOnHand(ref inputOrderAllocTableset);
  outputOrderAllocTableset = inputOrderAllocTableset;
});
4 Likes

@mikelyndersOKCC - thank you so much. really appreciated.
I haven’t used Functions yet but was looking at them yesterday - this is perfect!

@gunny72 no problem, I only got as far as I did because of help from this forum. Hope I can return the favor!