Function to create CustShip works from Schedule Epicor Function, but not from REST/Swagger. Are Update & MasterUpdate broken?

Hi all,

This is a very weird one, maybe you guys can help. I have a simple function that creates a customer shipment with just one line. Just creates it, doesn’t ship it. When I call the function from Schedule Epicor Function, it works perfectly. But when I call the same function from Postman or Swagger, it does not update the inventory tables (PartWhse, PartQty, etc.). Everything else works just fine. I look at the Picked Qty and Sales Picked Qty in Part Tracker for example, and it does not update them. More than that, if I go to Customer Shipment Entry and ship the pack by checking the Shipped checkbox, it does not update the on-hand qty from PartBin and PartWhse!

Below is the code. I have tried almost everything I could find in this forum but no luck. There are 4 save methods there (update or updatemaster), all of them work perfectly from Schedule Epicor Function, but from Postman or Swagger none of them update the inventory tables! Am I missing something or is it something wrong with Update/MasterUpdate when called from REST ?


// CALL CustShip SERVICE
this.CallService<Erp.Contracts.CustShipSvcContract>(csc => {

  // define custship tableset
  Erp.Tablesets.CustShipTableset tsCustShip = new Erp.Tablesets.CustShipTableset();
  

  // CREATE PACK
  try
  {
    csc.GetNewShipHead(ref tsCustShip);
    // set ShipVia and ThirdParty fields
    tsCustShip.ShipHead[0].ShipViaCode = "TNL";
    tsCustShip.ShipHead[0]["ThirdParty_c"] = "TigersNL";
    
    // order info
    csc.GetHeadOrderInfo(vSONo, out tstr, ref tsCustShip);
    btcustomerno = tsCustShip.ShipHead[0].BTCustNum;
    
    // legal nos
    csc.GetLegalNumGenOpts(0, ref tsCustShip, out obool);
    
    // pcbin
    csc.CheckPCBinOutLocation(ref tsCustShip, out oint, out obool, out ostr);
    
    // save
    csc.UpdateMaster(ref tsCustShip,false,false,false,false,false,false,false,0, btcustomerno, out opReleaseMessage, out opCompleteMessage, out opShippingMessage, out opLotMessage, out opInventoryMessage, out opLockQtyMessage, out opAllocationMessage, out opPartListNeedsAttr, out opLotListNeedsAttr, out shipCreditMsg, out cError, out compError, out msg, out opPostUpdMessage, out updateComplete, out checkComplianceError, out changeStatusError, out checkShipDtlAgain);
    
  }
  catch (Exception ex)
  {
    throw new Ice.BLException("[newpack]" + ex.Message);
  }

  
  // get newly created pack number
  vPackNo = tsCustShip.ShipHead[0].PackNum;
  
  // create new pack line
  csc.GetNewOrdrShipDtl(ref tsCustShip, vPackNo, 0);
  
  // get order info
  tsCustShip.ShipDtl[0].OrderNum = vSONo;
  csc.GetOrderInfo(vSONo, out ostr, ref tsCustShip);
  
  // get order line info
  tsCustShip.ShipDtl[0].OrderLine = vSOLine;
  csc.GetOrderLineInfo(ref tsCustShip, 0, vSOLine, String.Empty);
  
  // get order rel info
  tsCustShip.ShipDtl[0].OrderRelNum = vSORel;
  csc.GetOrderRelInfo(ref tsCustShip, 0, vSORel, false);
  
  // get part info
  tstr = vPartNo;
  tsCustShip.ShipDtl[0].PartNum = tstr;
  csc.GetPartInfo(ref tsCustShip, 0, ref tstr, Guid.Empty, String.Empty, out ostr, out ostr, out obool, out ostr);
  
  // set warehouse, bin and qty
  tsCustShip.ShipDtl[0].WarehouseCode = vWarehouse;
  csc.GetWhseInfo(ref tsCustShip, 0, vWarehouse, "WarehouseCode");
  
  // set bin
  tsCustShip.ShipDtl[0].BinNum = vBin;
  csc.ValidateBinCode(vWarehouse, vBin);
  
  // set qty
  csc.GetQtyInfo(ref tsCustShip, 0, vQty, 0);

  
  // save line
  try
  {
    // pcbin
    csc.CheckPCBinOutLocation(ref tsCustShip, out oint, out obool, out ostr);
   
   
    // SAVE 1
    //csc.Update(ref tsCustShip);
   
   
    // SAVE 2
    tsCustShip.ShipHead[0].RowMod = "U";
    csc.UpdateMaster(ref tsCustShip,false,true,false,false,false,false,false,vPackNo, btcustomerno, out opReleaseMessage, out opCompleteMessage, out opShippingMessage, out opLotMessage, out opInventoryMessage, out opLockQtyMessage, out opAllocationMessage, out opPartListNeedsAttr, out opLotListNeedsAttr, out shipCreditMsg, out cError, out compError, out msg, out opPostUpdMessage, out updateComplete, out checkComplianceError, out changeStatusError, out checkShipDtlAgain);
    
    
    // SAVE 3 (new row with 'U')
    /*
    oldRow = tsCustShip.ShipHead[0];
    var newRow = tsCustShip.ShipHead.NewRow();
    BufferCopy.CopyExceptFor(oldRow, newRow, "");
    newRow.RowMod = "U";
    tsCustShip.ShipHead.Add(newRow);
    csc.UpdateMaster(ref tsCustShip,false,true,false,false,false,false,false,vPackNo, btcustomerno, out opReleaseMessage, out opCompleteMessage, out opShippingMessage, out opLotMessage, out opInventoryMessage, out opLockQtyMessage, out opAllocationMessage, out opPartListNeedsAttr, out opLotListNeedsAttr, out shipCreditMsg, out cError, out compError, out msg, out opPostUpdMessage, out updateComplete, out checkComplianceError, out changeStatusError, out checkShipDtlAgain);
    */
    
    
    // SAVE 4 (new row with '')
    /*
    oldRow = tsCustShip.ShipHead[0];
    var newRow = tsCustShip.ShipHead.NewRow();
    BufferCopy.CopyExceptFor(oldRow, newRow, "");
    oldRow.RowMod = "U";
    tsCustShip.ShipHead.Add(newRow);
    csc.UpdateMaster(ref tsCustShip,false,true,false,false,false,false,false,vPackNo, btcustomerno, out opReleaseMessage, out opCompleteMessage, out opShippingMessage, out opLotMessage, out opInventoryMessage, out opLockQtyMessage, out opAllocationMessage, out opPartListNeedsAttr, out opLotListNeedsAttr, out shipCreditMsg, out cError, out compError, out msg, out opPostUpdMessage, out updateComplete, out checkComplianceError, out changeStatusError, out checkShipDtlAgain);
    */

  }
  catch (Exception ex)
  {
    throw new Ice.BLException("[saveln " + vPartNo + "]" + ex.Message);
  }
   

// END CALL CustShip SERVICE
});

Thanks!

Interesting that nobody replied to this one. @josecgomez or @Mark_Wonsil would you mind taking a look at this one when you get a chance? If anybody else has comments they are more than welcome.

This sound like a Deferred Update issue. Epicor sometimes does updates to certain part / inventory tables in a deferred way it basically stores the transaction in a queue for processing.

For example look in

SELECT * FROM erp.PartBinDeferred

Or any of these
image

Right after your swagger call and see if you see an entry there. If you do, you just have to wait for Epicor to catch up/.

Thank you for the reply, much appreciated! You are spot on, it creates records in PartBinDeferred and PartQtyDeferred. But it processes them after around 20-30 minutes, anything I can do to speed that up? Or I shouldn’t mess with it?

Dont mess with it, let Epicor be Epicor :slight_smile: There are ways in which you can speed it up, but its not worth it.

Here’s how just because but again not something you should worry about

1 Like

I played around with that a little but it doesn’t seem to work for me. Maybe because it is called from a function. But even if I make it work, I don’t want to think what will happen if it fails.
Anyway, I know what’s going on and it makes total sense. I think I am going to split this function in 2: first will dump the data into an UD table and the second will pick that up and create the pack. Maybe schedule the second one every 5 or 10 mins. Thanks a lot again!

Oh no, why not? I just implemented this code you referenced (though not in Production yet) for an inventory transfer app I’m working on.

Sorry if the cons have been addressed, but I don’t recall seeing it (just skimmed that thread again, too).

I mean it just speeds up something that Epicor is doing anyway, there is no real upside and the software already accounts for these. Is just more code to write and maintain.

@josecgomez So the reason I “needed” it was that, in testing the EFx in Postman, I wasn’t able to transfer the same part number twice in a row - even minutes apart. In all seriousness, what other way is there around that?

@Dragos I used Carson’s modified code here:

If you have that in a function, would you mind sharing the code? I tried both below but doesn’t seem to do the trick for me:

Epicor.Functions.IFunctionHost host = (Epicor.Functions.IFunctionHost)this.GetType().BaseType.BaseType.GetField("host", System.Reflection.BindingFlags.NonPublic |  System.Reflection.BindingFlags.Instance).GetValue(this);
var Dbl = (Erp.ErpContext)host.GetIceContext();
Erp.Internal.Lib.DeferredUpdate libDeferredUpdate = new Erp.Internal.Lib.DeferredUpdate(Dbl);
libDeferredUpdate.UpdPQDemand();

/*
var context = Ice.Services.ContextFactory.CreateContext<ErpContext>();
Erp.Internal.Lib.DeferredUpdate libDeferredUpdate = new Erp.Internal.Lib.DeferredUpdate(context);
libDeferredUpdate.UpdPQDemand();
*/

Oh it’s quite literally Carson’s version, but I pasted it at the end of this post for reference. Looks like what you have commented out.

I put that in a custom code widget at the very end and I do everything else in no-code widgets.

Plus I needed the assembly of Erp.Internal.Lib.DeferredUpdate

var context = Ice.Services.ContextFactory.CreateContext<ErpContext>();

Erp.Internal.Lib.DeferredUpdate libDeferredUpdate = new Erp.Internal.Lib.DeferredUpdate(context);
libDeferredUpdate.UpdPQDemand();

image

In Later versions you have to add some context for it to worknight, also in Functions 700+ you should be able to use a Context Creator to get around that Reflection stuff
Try this if the above doesn’t work

var Dbcontext = Ice.Services.ContextFactory.CreateContext();
 
Erp.Internal.Lib.ErpCallContext.Add(“PBOnHand-Post”,””); // PartBin
Erp.Internal.Lib.ErpCallContext.Add(“PQDemand-Post”,””); // Part Quantity
Erp.Internal.Lib.ErpCallContext.Add(“PLOnHand-Post”,””); // Part Lot
 
Erp.Internal.Lib.DeferredUpdate libDeferredUpdate = new Erp.Internal.Lib.DeferredUpdate(Dbcontext);
libDeferredUpdate.UpdPQDemand();
 
Erp.Internal.Lib.ErpCallContext.RemoveValue(“PBOnHand-Post”);
Erp.Internal.Lib.ErpCallContext.RemoveValue(“PQDemand-Post”);
Erp.Internal.Lib.ErpCallContext.RemoveValue(“PLOnHand-Post”);
1 Like

Thanks @josecgomez! What reference do I need for Erp.Internal.Lib.ErpCallContext ? I cannot seem to find it. I already have Erp.Internal.Lib.DeferredUpdate

I learn something new every day here… Thanks Jose.

Do widget based BO calls create a context?

Erp.Internal.Lib.Shared.dll

1 Like

The widget stuff works out of the box. So yes ish kinda.

2 Likes

Thanks

Hmmm… I’m getting this on the server side:

<![CDATA[System.Data.Entity.Core.EntityException: The underlying provider failed on Open. —> System.Transactions.TransactionManagerCommunicationException: Network access for Distributed Transaction Manager (MSDTC) has been disabled. Please enable DTC for network access in the security configuration for MSDTC using the Component Services Administrative tool. —> System.Runtime.InteropServices.COMException: The transaction manager has disabled its support for remote/network transactions. (Exception from HRESULT: 0x8004D024)
at System.Transactions.Oletx.IDtcProxyShimFactory.ReceiveTransaction(UInt32 propgationTokenSize, Byte propgationToken, IntPtr managedIdentifier, Guid& transactionIdentifier, OletxTransactionIsolationLevel& isolationLevel, ITransactionShim& transactionShim)
at System.Transactions.TransactionInterop.GetOletxTransactionFromTransmitterPropigationToken(Byte propagationToken)

Widgets Code
1 542

Hey, I’ll take it as a win.