Create new line and release from UBAQ custom code

Does this require the Advanced BPM Update Only option or do you use the BPM Update option?

Yes advanced BPM cause you are writing your own code.

My BAQ lists a few handy fields for me including the customer’s requested quantity and date for the order, as well as some internal fields like OrderNum, OrderLine andOrderRelNum.

To pull data from the BAQ into my BPM I use this custom code to assign current row values to internal variables. There is a lot of if/then work to make sure I am updating with the correct values.

var ttResults_xRow = (from ttResults_Row in ttResults where ttResults_Row.Calculated_NewRelease == true select ttResults_Row).FirstOrDefault();

if (ttResults_xRow != null) // make sure there are more BAQ rows to process
{
  if (myrow <= totalrows) // make sure there are more BAQ rows to process
    {
    if (IsNewLine == true)
    {
      // we just added a new line. check to see if the next rel to add is for the same order and line.
      // (have to match on xrev, because the BAQ doesnt know what line number the new line is.)
      // if it is then, add the rel to the existing order/line.
        if (MyOrder == ttResults_xRow.OrderHed2_OrderNum && MyXRev == ttResults_xRow.Calculated_MyXRev)
        {
        //MyLine is already set from SetMyLine custom code widget
        MyQty = ttResults_xRow.Calculated_CustFinalQty;
        MyDate = ttResults_xRow.UD03_Date01;
        MyPart = ttResults_xRow.UD03_Key4;
        MyDescription = ttResults_xRow.UD03_ShortChar02;
        MyPrice = ttResults_xRow.UD03_Number02;
        MyRev = ttResults_xRow.OrderDtl5_RevisionNum;
        MyXRev = ttResults_xRow.Calculated_MyXRev;
        MyShipTo = ttResults_xRow.UD03_ShortChar01;
        ttResults_xRow.Calculated_NewRelease = false;
        IsNewLine = false;
        ClosedOrder = false;
        ClosedLine = false;
         }
         // if it is not then, add the rel to the order/line suggested by the BAQ.
         else
         {
        MyOrder = ttResults_xRow.OrderHed2_OrderNum;
          if (String.IsNullOrEmpty(Convert.ToString(ttResults_xRow.OrderDtl1_OrderLine)))
          {
            MyLine = 0;
          }
          else
          {
            MyLine = ttResults_xRow.OrderDtl1_OrderLine;
          }
        MyQty = ttResults_xRow.Calculated_CustFinalQty;
        MyDate = ttResults_xRow.UD03_Date01;
        MyPart = ttResults_xRow.UD03_Key4;
        MyDescription = ttResults_xRow.UD03_ShortChar02;
        MyPrice = ttResults_xRow.UD03_Number02;
         MyRev = ttResults_xRow.OrderDtl5_RevisionNum;
        MyXRev = ttResults_xRow.Calculated_MyXRev;
        MyShipTo = ttResults_xRow.UD03_ShortChar01;
        ClosedOrder = ttResults_xRow.OrderHed2_OpenOrder;
        ClosedLine = ttResults_xRow.OrderDtl1_OpenLine;
        ttResults_xRow.Calculated_NewRelease = false;
        IsNewLine = false;
         }
    }
    else
    {
    //not a new line, if xrevs match, use order number from BAQ and use existing line number,  otherwise create new line from baq line
      if (MyOrder == ttResults_xRow.OrderHed2_OrderNum && MyXRev == ttResults_xRow.Calculated_MyXRev)
      {
        MyQty = ttResults_xRow.Calculated_CustFinalQty;
        MyDate = ttResults_xRow.UD03_Date01;
        MyPart = ttResults_xRow.UD03_Key4;
        MyDescription = ttResults_xRow.UD03_ShortChar02;
        MyPrice = ttResults_xRow.UD03_Number02;
         MyRev = ttResults_xRow.OrderDtl5_RevisionNum;
        MyXRev = ttResults_xRow.Calculated_MyXRev;
        MyShipTo = ttResults_xRow.UD03_ShortChar01;
        ttResults_xRow.Calculated_NewRelease = false;
        ClosedOrder = false;
        ClosedLine = false;
      }
      else
      {
      MyOrder = ttResults_xRow.OrderHed2_OrderNum;
        if (String.IsNullOrEmpty(Convert.ToString(ttResults_xRow.OrderDtl1_OrderLine)))
        {
          MyLine = 0;
        }
        else
        {
          MyLine = ttResults_xRow.OrderDtl1_OrderLine;
        }
      MyQty = ttResults_xRow.Calculated_CustFinalQty;
      MyDate = ttResults_xRow.UD03_Date01;
      MyPart = ttResults_xRow.UD03_Key4;
      MyDescription = ttResults_xRow.UD03_ShortChar02;
      MyPrice = ttResults_xRow.UD03_Number02;
       MyRev = ttResults_xRow.OrderDtl5_RevisionNum;
      MyXRev = ttResults_xRow.Calculated_MyXRev;
      MyShipTo = ttResults_xRow.UD03_ShortChar01;
      ClosedOrder = ttResults_xRow.OrderHed2_OpenOrder;
      ClosedLine = ttResults_xRow.OrderDtl1_OpenLine;
      ttResults_xRow.Calculated_NewRelease = false;
      }
    }
   
    }
}

After this, I use SalesOrder BO methods to open/close orders/lines, add and update releases.

In these methods I always reference my internal variable for things like order and line number.
I hope this helps a little. I’d be happy to answer any other questions that I can!

1 Like

Jose’s the guru so I can’t add anything to what he’s said, but I can vouch for the soundness of the approach. If you leverage the Advanced BPM Update functionality in the uBAQs so much becomes doable that isn’t otherwise, and as he says, the BAQs don’t care where the data comes from or where you send it.

We do quite a lot with a couple of online services, and I’ve found you can interact with their REST APIs within the uBAQ code very neatly in both directions - push data in to be loaded or updated in Epicor, or fetch from the online data and return it to the user as if it was in the Epicor database. It’s very clean and re-purposable. And all the interaction with Epicor BOs happens right where it’s most convenient, within Epicor.

I’m looking forward to seeing whether the new Functions in .500 tip the balance against us needing an integration platform going forward.

2 Likes

funny, I was actually just reading a post of yours about it. I’ve created the uBAQ on Company with a single calculated field (for now) that would get set during the PATCH.



using the advanced BPM updated, pre-processing on Update. It just writes a msg to the log. I did also enable it, despite the screenshot.

When I update the row with placing a value in my calulated field, and click Update, I get this exception

Application Error

Exception caught in: Epicor.ServiceModel

Error Detail 
============
Message: There is no BPM customization attached to Update method of 'dev_RunRemoteProc' updatable query in 'JRF' company or BPM system is not enabled.
Please check presence of BPM customization and/or ask administrator for assistance.
Program: Epicor.ServiceModel.dll
Method: ShouldRethrowNonRetryableException

Client Stack Trace 
==================
   at Ice.Services.BO.DynamicQuery.Internal.Ubaq.UbaqCoreDefaultImpl.ThrowNotImplemented(DynamicQueryTableset queryDS, String methodName) in C:\_Releases\ICE\ICE3.2.200.3\Source\Server\Services\BO\DynamicQuery\Internal\Ubaq\UbaqCoreDefaultImpl.cs:line 19
   at Epicor.Customization.Bpm.MethodCustomizationBase2`3.RunDirectives(TParam parameters) in C:\_Releases\ICE\RL3.2.200.0\Source\Server\Internal\Lib\Epicor.Customization.Bpm\MethodCustomizationBase2.cs:line 197
   at Epicor.Customization.Bpm.CustomizationBase2`3.Execute(TParam parameters) in C:\_Releases\ICE\RL3.2.200.0\Source\Server\Internal\Lib\Epicor.Customization.Bpm\CustomizationBase2.cs:line 73
   at Epicor.Customization.Bpm.Ubaq3812EFA82E8F4145AE801D2EA37F5C87.JRF_dev_RunRemoteProcSvcCustomization.Update(DynamicQueryTableset queryDS, DataSet& queryResultDataset)
   at Ice.Services.BO.DynamicQuery.Internal.Ubaq.UbaqBpmCaller.Update(DynamicQueryTableset queryDesign, DataSet& queryResultDataset) in C:\_Releases\ICE\ICE3.2.200.3\Source\Server\Services\BO\DynamicQuery\Internal\Ubaq\UbaqBpmCaller.cs:line 101
   at Ice.Services.BO.DynamicQuerySvc.Update(DynamicQueryTableset queryDS, DataSet queryResultDataset) in C:\_Releases\ICE\ICE3.2.200.3\Source\Server\Services\BO\DynamicQuery\DynamicQuery.cs:line 611
   at Ice.Services.BO.DynamicQuerySvcFacade.Update(DynamicQueryTableset queryDS, DataSet queryResultDataset) in C:\_Releases\ICE\ICE3.2.200.3\Source\Server\Services\BO\DynamicQuery\DynamicQuerySvcFacade.cs:line 779
   at SyncInvokeUpdate(Object , Object[] , Object[] )
   at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
   at Epicor.Hosting.OperationBoundInvoker.InnerInvoke(Object instance, Func`2 func) in C:\_Releases\ICE\ICE3.2.200.3\Source\Framework\Epicor.System\Hosting\OperationBoundInvoker.cs:line 59
   at Epicor.Hosting.OperationBoundInvoker.Invoke(Object instance, Func`2 func) in C:\_Releases\ICE\ICE3.2.200.3\Source\Framework\Epicor.System\Hosting\OperationBoundInvoker.cs:line 47
   at Epicor.Hosting.Wcf.EpiOperationInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs) in C:\_Releases\ICE\ICE3.2.200.3\Source\Framework\Epicor.System\Hosting\Wcf\EpiOperationInvoker.cs:line 23
   at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)

   at Epicor.ServiceModel.Channels.ImplBase`1.ShouldRethrowNonRetryableException(Exception ex, DataSet[] dataSets)
   at Ice.Proxy.BO.DynamicQueryImpl.Update(DynamicQueryDataSet queryDS, DataSet queryResultDataset)
   at Ice.Adapters.DynamicQueryAdapter.<>c__DisplayClass27_0.<Update>b__0(DataSet datasetToSend)
   at Ice.Adapters.DynamicQueryAdapter.ProcessUbaqMethod(String methodName, DataSet updatedDS, Func`2 methodExecutor, Boolean refreshQueryResultsDataset)
   at Ice.Adapters.DynamicQueryAdapter.Update(DynamicQueryDataSet queryDS, DataSet updatedDS, Boolean refreshQueryResultsDataset)
   at Ice.UI.App.BAQDesignerEntry.BAQTransaction.<CallUpdate>b__378_0(Int32& rowReturned)
   at Ice.UI.App.BAQDesignerEntry.Forms.BAQDiagramForm.ShowQueryResults(DataSet dsResults, getQueryResult getResults, ReportAdditionalInfo additionalInfo)
   at Ice.UI.App.BAQDesignerEntry.BAQTransaction.CallUpdate()

I’ve searched the forum a bit and this seems to be an oddity…any ideas?

Make sure you click the “Enable” checkbox in your method directives.

It’s enabled. I can also see the source files on the server, so I know it’s there too

Sometimes I would get this error if I didn’t have a blank method in the Update base directive. Just add a new base method to the update and insert a blank custom code element (with just a comment), then enable this empty method on update. It’s worth a shot! :slight_smile:

1 Like

Oooo, that worked. That’s dirty

2 Likes

Once you’ve opted for Advanced BPM you might as well put everything in Update Base anyway rather than pre-processing. There is no base otherwise, as you’ve found.

1 Like

Got it working nicely with uBAQ and Advanced Update Processing.
Basically, I set up my uBAQ on Company like Jose suggested to return 1 row. I then created my “input” fields as calculated fields and gave them default values


I made the uBAQ updatable and set the calculated fields to updatable.
I then created a base processing directive (thanks @dhewi and @NateS) with my custom code


I added a reference to Erp.Contracts.BO.SalesOrder using the “Using and References” functionality.
Then, I wrote out my code with a little debugger in it too (add using System.IO if you want to use that)

To invoke, run a GetList in the BAQ editor and then double click the row to update the fields.
Note: my web app is calling other BAQs to get the valid combo of CustNum, BTCustNum, ShipToCustNum, etc. so that it’s not junk data. Here is where the API would be setting these fields, which I won’t go into right now.


After the fields are set, I click Update to invoke the Update method of this BAQ, which calls the custom code.

This is blazing fast and allows me to add any business specific logic server side, while also insulating the web app from changes to Epicor, such as the complex datasets that are associated with the direct BO calls and the API version, to some extent.

10 Likes

Glad it works nicely!

When you upgrade to 500+ you can easily turn this into a Function (no more UBAQ needed) :slight_smile:

4 Likes

Hey just wanted to say to everyone that contributed to this Topic, thank you ! This is the most helpful and engaged community I’ve ever been apart of.

We’re now at the point where we are ready to redo our internal API (moving to python) and I suggested using UBAQs/functions as the new way to interface with Epicor. (Current interface is still based on legacy services (SOAP) in .Net) I found this topic to be the most helpful! First UBAQ? updating a customer shipment to shipped!

1 Like

I’ve just read this post with interest, and will be trying a uBAQ to replace custom code client side. I have a query on this:

Instead of clicking Actions > Run Custom Action, they can just click the big button in the middle of the screen.

Is that a custom button, and if so what code is behind it to call the custom actions from a BAQ?

Thanks!

Hi Mark,
Yes, the button is added in customization. The code for it runs my custom action as below.
I hope this helps! Good luck!
Nate

private void epiButtonC1_Click(object sender, System.EventArgs args)
{
	//Call Actions
	oTrans.PushStatusText("Running Custom Actions...", false);
	//check to see if there are any boxes checked in OurUnmatched, if so, then execute custom action
	oTrans.PushStatusText("Processing commands for our unmatched orders...", false);
	//V_OurUnmatchedOrders_faster_1View
	var edv = oTrans.Factory("V_OurUnmatchedOrders_faster_1View");
	BAQRunCustomAction(edv, "ProcessCommands");
	//check to see if there are any boxes checked in Unmatched, if so, then execute custom action
	oTrans.PushStatusText("Processing commands for unmatched orders...", false);
	//V_OpenOrderRequests_Fast_1View1
	edv = oTrans.Factory("V_OpenOrderRequests_Fast_1View");
	BAQRunCustomAction(edv, "ProcessCommands");
	
	//Call Dash Refresh All
	RefreshAllBAQs();
}

void RefreshAllBAQs()
{
	oTrans.PushStatusText("Refreshing all views. Please be patient...", false);      
	MainController.AppControlPanel.HandleToolClick("RefreshAllTool", new 
	Infragistics.Win.UltraWinToolbars.ToolClickEventArgs(MainController.MainToolManager.Tools["RefreshAllTool"], null)); 
	oTrans.PushStatusText("Done!", false);
	
}

 void BAQRunCustomAction(EpiDataView iEdv, string iActionID)
{
	BAQDataView BAQView = (BAQDataView)iEdv;
	Assembly assembly = Assembly.LoadFrom("Ice.Lib.EpiClientLib.dll");
	Type t = assembly.GetType("Ice.Lib.Framework.BAQUpdater");
	MethodInfo mi = t.GetMethod("BAQRunCustomAction", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
	mi.Invoke("Ice.Lib.Framework.BAQUpdater", new object[]{BAQView, iActionID});
}

If anyone might be interested, I’ve attached a UBAQ export based on your notes above.

UBAQ-SalesOrder.baq (28.3 KB)
Note that I substituted the path as @“C:\TEMP\BPMDebugger.txt”
and had to verify the TermsCode “N30” existed in E10.

Thanks for your original example and for comments by others.
Used to be very difficult to find examples before this site was born.
The ICE Tools User Guide will only get a person so far.

2 Likes

Nice sample code, can you share how to add OrderDtls too (after /Create OrderHed/) please!
Thanks.

If you’re on kinetic, definitely use a function for this instead of this hacky baq method way :slight_smile: I have written up some decent examples of how to use a single function call and creating an order head and order details

2 Likes

In fact I am currently passing a workflow from service connect to functions, to generate a Sales Order. I visit this site frequently looking for some “ideas”. :smile:

I have a Function that calls an external API and get data to generate a Sales Order, from that same function I generate the Json and send it to the Kinetic API to create the Sales Order, it works correctly, but I read that it is more appropriate in Functions, to call services instead of Rest API calls.

This is my code to add lines, based in your sample code:
Thanks!