Using Epicor Transaction Scopes In BPMs

Epicor ERP - Kinetic, v10, & v9

I was recently tasked by one of my customers to create a BPM that, upon shipping a transfer order, would automatically create a GL journal entry that moved the cost of their internal freight from the sending location to the destination. And while that may sound daunting, if you follow the steps here to understand how to use business objects within a BPM it ends up just being some code that looks like this:

// Custom Code
///////////////////////////////////////////////////////////////////////////////
var jrnGrp = ServiceRenderer.GetService<Erp.Contracts.GLJrnGrpSvcContract>(Db);
var jrn = ServiceRenderer.GetService<Erp.Contracts.GLJournalEntrySvcContract>(Db);

Erp.Tablesets.GLJrnGrpTableset jrnGrpTs = new Erp.Tablesets.GLJrnGrpTableset();
Erp.Tablesets.GLJournalEntryTableset jrnTs = new Erp.Tablesets.GLJournalEntryTableset();

string groupId = "MYGROUP";

// Create the journal group
jrnGrp.GetNewGLJrnGrp(ref jrnGrpTs);
jrnGrpTs.GLJrnGrp[0].GroupID = groupId;
jrnGrp.Update(ref jrnGrpTs);

// Add a new journal to the group
jrn.GetNewGlJrnHedTran(ref jrnTs, groupId);
bool requiresUserInput;
jrnTs.GLJrnHed[0].Description = "My Cool Journal Entry";
jrn.PreUpdate(ref jrnTs, out requiresUserInput);
jrn.Update(ref jrnTs);
int journalNum = jrnTs.GLJrnHed[0].JournalNum;
    
// Add the credit (GL account and amount hard coded for example)
jrn.GetNewGLJrnDtlMnl(ref jrnTs, "MAIN", DateTime.Now.Year, "", "GJ", journalNum, 0);
bool currCodeChanged;
jrnTs.GLJrnDtlMnl[0].GLAccount = "12345|AA|123|0";
jrn.ChangeGlAcct1(1, ref jrnTs, out currCodeChanged);
jrnTs.GLJrnDtlMnl[0].TotCredit = (decimal)100.00;
jrn.Update(ref jrnTs);

// Add the debit
jrn.GetNewGLJrnDtlMnl(ref jrnTs, "MAIN", DateTime.Now.Year, "", "GJ", journalNum, 0);
jrnTs.GLJrnDtlMnl[1].GLAccount = "12345|BB|123|0";
jrn.ChangeGlAcct1(2, ref jrnTs, out currCodeChanged);
jrnTs.GLJrnDtlMnl[1].TotDebit = (decimal)100.00;
jrn.Update(ref jrnTs);
  
// Post it
string notAllPosted;
jrnGrp.CheckBeforePost(groupId);
jrnGrp.PostGroupJournals(groupId, out notAllPosted);
jrnGrp.UnlockGroup(groupId);
///////////////////////////////////////////////////////////////////////////////

But when I had a variation of this code in place I got this error:

The underlying provider failed on EnlistTransaction.

Clear as mud, right? You can read up on this online and get even more confused if you like, but in short Epicor is telling us to wrap our call in a transaction. This is the same sort of idea you would find in the SQL world where you put your logic into a transaction block and if anything fails it rolls everything back. With something like a journal entry, it makes complete sense that you might have a lot of cascading logic occurring and this unclear error is actually making a very clear and sensible suggestion to you. Transactions are pretty easy within BPMs - all you’ve got to do is wrap your code inside of a using statement where you validate and complete the transaction within (I chopped down the code from above just so you can see contextually where the new lines go):

// Put this using line in where transactions really start to occur
using (var txscope = IceDataContext.CreateDefaultTransactionScope())
{
  // Create the journal group
  jrnGrp.GetNewGLJrnGrp(ref jrnGrpTs);

  ...
  
  // Post it
  jrnGrp.UnlockGroup(groupId);
  
  // Use Db.Validate() to make sure the transaction is all good
  Db.Validate();
  
  // Complete the transaction
  txscope.Complete();
}

And that’s it, my problem went away and I was able to see those nice journal entries automatically being written. Hope this helps!