InvTransferSvcContract.CommitTransfer fails when transferring the same part one after the other

I have an Updatable Dashboard that uses an Advanced BPM to trigger an Inventory Transfer.

Part 123 is updated, and transferred.
Part 123 is updated (separate row/material card), and fails on CommitTransfer. The PreCommit is the last step completed.

Alternatives such as these work:
Part 123 is updated, and transferred.
Part 456 is updated, and transferred.

and

Part 123 is updated, and transferred.
Save.
Part 123 is updated (separate row, material card), and transferred.

Any help would be appreciated.

Business Layer Exception

Error creating PartTran. Please retry.

Exception caught in: Epicor.ServiceModel

Error Detail

Description: Error creating PartTran. Please retry.
Program: Erp.Services.BO.InvTransfer.dll
Method: doPartTran
Line Number: 1482
Column Number: 33
Table: PartTran
Server Trace Stack: at Erp.Services.BO.InvTransferSvc.doPartTran(String ipPartIUM, Boolean ipPartTrackDimension, String ipPCID, String& partTranPK) in C:_Releases\ERP\UD10.1.600.26\Source\Server\Services\BO\InvTransfer\InvTransfer.cs:line 1482
at Erp.Services.BO.InvTransferSvc.CommitTransfer(InvTransferTableset& ds, String& LegalNumberMessage, String& partTranPKs) in C:_Releases\ERP\UD10.1.600.26\Source\Server\Services\BO\InvTransfer\InvTransfer.cs:line 1193
at Erp.Services.BO.InvTransferSvcFacade.CommitTransfer(InvTransferTableset& ds, String& LegalNumberMessage, String& partTranPKs) in C:_Releases\ERP\UD10.1.600.26\Source\Server\Services\BO\InvTransfer\InvTransferSvcFacade.cs:line 162
at Epicor.Customization.Bpm.Ubaq84A2DE00F16B4DF49D364961CCF18569.UpdateBaseDirective_Transfer_on_Update_D9D461FFB07D49CE9B8EAC6661737446.A001_CustomCodeAction()

Didn’t we fix this one by changing the scope of your InvTransfer BO to inside the tt loop?

That fixed it so we can do multiple row updates without object problems. Unfortunately it is not fixing the scenario described above.

@bfraseraepl - did you ever find a fix for this? I am facing the same issue.

Unfortunately, no. Where are you calling your code from?

can you call a save on field leave? Since saving works.

Secondly, is the BAQ set to allow multiple rows? In a dashboard, if the baq doesn’t allow multiple rows, it will save on leaving that row. Which is usually intended behavior. It basically would do what your “save” in between rows.

Update method on an updatable BAQ. I can’t seem to get the second transaction to process. At this point I’m just running the baq on UD100 to test with.

I’m looking to switch to doing it with adapter calls in the UI.

Here is my current code:
{
string msg = “”;
string ptmsg = “”;
bool reqUserInput = false;

foreach(var ttResultsRow in (from row in ttResults where row.RowMod == “U” select row))
{

      msg = "";
      ptmsg = "";
      Erp.Contracts.InvTransferSvcContract invTrans = null;
      invTrans = Ice.Assemblies.ServiceRenderer.GetService<Erp.Contracts.InvTransferSvcContract>(Db);
      var InvTransferDS = new Erp.Tablesets.InvTransferTableset();
      InvTransferDS = invTrans.GetTransferRecord(ttResultsRow.UD100_ShortChar01, ttResultsRow.UD100_ShortChar08);
      var ttInvTrans_Row = (from InvRow in InvTransferDS.InvTrans select InvRow).FirstOrDefault();
      if(ttInvTrans_Row !=null)
        {
          ttInvTrans_Row.TransferQty = ttResultsRow.UD100_Number01; 
          ttInvTrans_Row.TrackingQty = ttResultsRow.UD100_Number01; 
          ttInvTrans_Row.FromWarehouseCode = ttResultsRow.UD100_ShortChar02; 
          ttInvTrans_Row.FromBinNum = "";
          ttInvTrans_Row.ToWarehouseCode = ttResultsRow.UD100_ShortChar04;
          ttInvTrans_Row.FromBinNum = ttResultsRow.UD100_ShortChar03;
          ttInvTrans_Row.TranReference = ttResultsRow.UD100_ShortChar06;         
          ttInvTrans_Row.RowMod = "U"; 
          invTrans.ChangeUOM(ref InvTransferDS); 
          invTrans.ChangeFromWhse(ref InvTransferDS);
          ttInvTrans_Row.FromBinNum = ttResultsRow.UD100_ShortChar03;    
          invTrans.ChangeFromBin(ref InvTransferDS);        
          invTrans.ChangeToWhse(ref InvTransferDS); 
          ttInvTrans_Row.ToBinNum = ttResultsRow.UD100_ShortChar05; 
          invTrans.ChangeToBin(ref InvTransferDS); 
          invTrans.PreCommitTransfer(ref InvTransferDS, out reqUserInput); 
          invTrans.CommitTransfer(ref InvTransferDS, out msg, out ptmsg);
        }
      invTrans.Dispose();
      //hInvTransfer = null;
      InvTransferDS = null;


  }

}

If it helps, here is where the failure seems to occur:
Create_PartTran(ref PartTran, MainDate, MainTime, out PartTranError);

Digging way deeping to the bowels of Epicor I see some other logic which probably not well suited for the forum but it has me thinking.

//Epicor is using these values to pass to create a part tran - as it has to be unique
MainDate = CompanyTime.Now().Date;
MainTime = CompanyTime.Now().SecondsSinceMidnight();

What happens if you try to pass the same pn across in the same second, as I would assume happens. I bet it would fail. If you want to test this theory, either place several other pn records in between you same pn call or (FOR TESTING ONLY IN A TEST ENV) add a delay in your processing to see if that stops the issue. If it does, we’d need to talk to Epicor about a fix.

example1:
pn A
a butload of other unique pn’s
pn A

example 2
pn A
Thread sleep 4 seconds
pn A

If not, I wonder if it’s possible that its always pulling the same time in the BPM call (like a snapshot of when it was called). Meh, one test at a time.

Might be worth it to ping an inside man who can offer some suggestion (or tell you to avoid mine haha) @Rich

1 Like

I threw a 5 second pause in the beginning of the loop.

System.Threading.Thread.Sleep(5000);

My issue seems to happen even for different part numbers. It will do the first transfer but won’t do a second. I’m trying with two different part records.

The first record (the one that works) does write to parttran and the systime is different for the removal and add record of that transfer. The second part record never makes it.

I’m stumped.

I think in some cases of your Change events, the BO will remove your “U” row mod and you willl have to reset it. Also, are you getting a specific exception?

Exactly the same as the original on this post:

Description: Error creating PartTran. Please retry.

Have you tried placing a MessageBox just to see each PN\UOM as it’s being processed. I know I’ve been smacked plenty of times where the data coming in wasnt quite what I “KNEW” it was lol

Also, I cant recall, maybe @bfraseraepl could confirm, but we may have wrapped our BO and it’scalls in a TransactionScope.

Adding the txscope makes it work for different part numbers but not for the same part number. Very weird…

What version are we talking here? If it helps:
image

Waaaaait a minute, that a different error than I was looking at - the “retry” part is different. Retracing my steps now.

The problem seems to be with:
ExistsPartBinDeferred(Session.CompanyID, Part.PartNum, MainWhse, MainBin, ttInvTrans.TrackingUOM, MainLot, ttInvTrans.PCID)

The system will try up to 10 times to ensure a deferred part bin doesnt exist, pausing 1s between each attempt. After 10 seconds, it kaputs that error. Perhaps a Db.Validate after each commit will solve it?

No dice. I added the Db.Validate();

It does seem to take about 10 seconds before the error appears so I think you have the right issue.

Thank you so much for assisting with this.

Sure thing, I enjoy the chase. Although I havent accomplished much yet. It seems the PartBinDeferred is not an exposed table. I can also see it has it’s secret squirrel library with (internal) classes like ProcessDeferredUpdates.

I see that the PerformMaterialMovement potentially calls several methods from the lib, which I assume handles all of the little automagic things Epicor does in the backgrounds when you move\issue parts\lots\etc:
LibDeferredUpdate.PostPBOnHand
LibDeferredUpdate.PostPLOnHand

So, it seems the BO should be handling all of this. My guess is the transaction scope of the BPM is the limiting factor. I would imagine that in your foreach loop of your records to transact, if you had each iteration in a TxScope AND Got a new instance to the IssueBO each time, did the transaction and then completed the transaction scope - it MIGHT work. Let me know, curiosity runs deep.

I think I’m doing that currently but I might have it wrong. I have txscope, then inside calling the bo and at the end of the tx I am nulling out the bo dataset and disposing the bo.

  foreach(var ttResultsRow in (from row in ttResults where row.RowMod == "U" select row))
      {

          using (var txScope = IceContext.CreateDefaultTransactionScope())
            {
            
                msg = "";
                ptmsg = "";
                Erp.Contracts.InvTransferSvcContract invTrans = null;
                invTrans = Ice.Assemblies.ServiceRenderer.GetService<Erp.Contracts.InvTransferSvcContract>(Db);
                var InvTransferDS = new Erp.Tablesets.InvTransferTableset();
                InvTransferDS = invTrans.GetTransferRecord(ttResultsRow.UD100_ShortChar01, ttResultsRow.UD100_ShortChar08);
                var ttInvTrans_Row = (from InvRow in InvTransferDS.InvTrans select InvRow).FirstOrDefault();
                
                this.PublishInfoMessage(ttInvTrans_Row.PartNum, Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual, "", "");
                if(ttInvTrans_Row !=null)
                  {
                    ttInvTrans_Row.TransferQty = ttResultsRow.UD100_Number01; 
                    ttInvTrans_Row.TrackingQty = ttResultsRow.UD100_Number01; 
                    ttInvTrans_Row.FromWarehouseCode = ttResultsRow.UD100_ShortChar02; 
                    ttInvTrans_Row.FromBinNum = "";
                    ttInvTrans_Row.ToWarehouseCode = ttResultsRow.UD100_ShortChar04;
                    ttInvTrans_Row.FromBinNum = ttResultsRow.UD100_ShortChar03;
                    ttInvTrans_Row.TranReference = ttResultsRow.UD100_ShortChar06;         
                    ttInvTrans_Row.RowMod = "U"; 
                    invTrans.ChangeUOM(ref InvTransferDS); 
                    invTrans.ChangeFromWhse(ref InvTransferDS);
                    ttInvTrans_Row.FromBinNum = ttResultsRow.UD100_ShortChar03;    
                    invTrans.ChangeFromBin(ref InvTransferDS);        
                    invTrans.ChangeToWhse(ref InvTransferDS); 
                    ttInvTrans_Row.ToBinNum = ttResultsRow.UD100_ShortChar05; 
                    invTrans.ChangeToBin(ref InvTransferDS); 
                    invTrans.PreCommitTransfer(ref InvTransferDS, out reqUserInput);
                    ttInvTrans_Row.RowMod = "U"; 
                    invTrans.CommitTransfer(ref InvTransferDS, out msg, out ptmsg);
                    Db.Validate();
                  }
                invTrans.Dispose();
                InvTransferDS = null;
           txScope.Complete(); 
           }

      }

Yep, that’s exactly what I was describing. Well, back to the drawing board.

Do you think I’d have better luck using adapter calls in a UI customization? that was my next try. Iterate over a grid.