BPM on Button Click Event

Hi Epicor community,

I need t trigger a BPM when a custom button is clicked.
This button is on a custom tab in job entry on job operation level.
The purpose of this button is to copy some information from related Sales Order. I will not confuse you with the related logic.
The problem that I am facing right now is that this topic is the closest solution that I have found however is not good enough:

The reason is that using a Method directive, this solution will update all rows that are linked to that job on JobHead (please correct me if I am making a mistake here) while I need to update a specific record on JobOper table.
So using the RowMod seems incorrect.
Update: @dr_dan made a good point that there is no issue with updating JobHead. That is true, What I was trying to explain and I failed, is that if you look at the screenshot, there are 4 operations and we want to specifically update one of these with the new information from Sales Order at a time, not all, imagine a scenario where we have manual entry for operation 10 but we want to copy info from sales order to operation 20.

Is there anyway to tackle this issue?
We do need to have a button to give user the ability to choose whether they need to update the record (so triggering update method via save button is also not favorable).

This is the customized form for your review :

Many thanks in advance

I don’t understand why you don’t want to update the JobHead. I don’t know what all gets updated, but if you were to manually update that form, the system would call JobEntry.Update() and it would update the JobOper table. Would it pick up something and update in JobHead also? If yes, why is that a deal breaker?

Depending what version you are on create a function to do the work and call that on the button click

Hi @dr_dan ,

No that is absolutely ok, I see what you are saying.
As long as that specific Operation gets updated with the new information from Sales Order and not other Operations it is perfect. :slight_smile:

So how would I go about that?

Hi @tkoch ,

We are on Epicor 10.2.600.30.
By function do you mean a method in the C#/V# side? Sorry I am not familiar with that terminology within Epicor.
Would you be able to refer me to a reference to learn from or maybe a working example?

Are all those fields User Defined columns in JobOper? If so, have you tried just setting them all with the button click and then you should just be able to click the save button. If you want it to save automatically after clicking that button and populating the form, put oTrans.Update() at the end of the button click code.

Hi @dr_dan
Regarding the oTrans.Update() already acceptable, no issue
How would I retrieve the info from Sales Order? I have not done this via Customization before.
In BPM it is using tt tables, I really appreciate you educating me :slight_smile:

It looks like you are in a make-to-order job… and that’s what I would expect if you’re trying to pull extra order info into the job. I would set up a BPM on the method directive JobEntry.Update(). I would think it would be pre-processing. Then on the button click event in your customization, I’d set the JobOper.RowMod = “U” and callContextBpmData.Character01 something like UPDATE ORDER. Then I’d put a condition block in your BPM that looks for an updated row in ttJobOper and looks for callContextBpmData.Character01 to = UPDATE ORDER. This will be the trigger that allows the BPM to go get the info from the order and copy it to the ttJobOper table. I’m not sure what data will be available in the tt tables… but I imagine ttJobProd should have your order info in it. Assuming it does, you’d be able to write a LINQ statement for each field you need to copy and you’ll be able to find the sales order using the info in the ttJobProd table. How detailed do I need to go here?

This is very good @dr_dan
callContextBpmData.Character01 seems to be what I was looking for
I will test it quickly and will get back to you here, it might take an hour or so

thank you very much

Hi @dr_dan
I seem to be facing issues setting both RowMod and CallContext
Could you please provide a working example for setting these two?

Many thanks :slight_smile:

You’re going to need to instantiate the epiDataViews for each of those. This is easy using the Form Event Wizard in the customization screen. Pick EpiViewNotification for both the callContextBpmData and JobOper and add them to the custom code.

It should’ve created two variables (1 for each). They should be edvCallContextBpmData and edvJobOper. To set the rows you will follow this syntax

edvJobOper.dataView[edvJobOper.Row]["RowMod"] = "U";
edvCallContextBpmData.dataView[edvCallContextBpmData.Row]["Character01"] = "UPDATE ORDER";
oTrans.Update();

Put that in your button click event. I am writing this all from memory so there might be slight variations in the labels on the wizards/variable naming convention. But this should get you close. If it’s working correctly, this will fire the BPM you put on JobEntry.Update.

1 Like

I almost forgot. In the BPM, you should have a widget set the Character01 field in the callContextBpmData back to “”. Otherwise it might keep firing for subsequent updates on that job while still in the same screen.

Thank you for letting me know
Makes total sense :slight_smile:

1 Like

Hi @dr_dan ,
This is what I tried

// **************************************************
// Custom code for JobEntryForm
// Created: 10/03/2022 3:24:47 PM
// **************************************************

extern alias Erp_Contracts_BO_Project;
extern alias Erp_Contracts_BO_Part;

using System;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Windows.Forms;
using Erp.Adapters;
using Erp.UI;
using Ice.Lib;
using Ice.Adapters;
using Ice.Lib.Customization;
using Ice.Lib.ExtendedProps;
using Ice.Lib.Framework;
using Ice.Lib.Searches;
using Ice.UI.FormFunctions;

public class Script
{
	// ** Wizard Insert Location - Do Not Remove 'Begin/End Wizard Added Module Level Variables' Comments! **
	// Begin Wizard Added Module Level Variables **

	private EpiDataView edvJobOper;
	private EpiDataView edvCallContextBpmData;
	// End Wizard Added Module Level Variables **

	// Add Custom Module Level Variables Here **

	public void InitializeCustomCode()
	{
		// ** Wizard Insert Location - Do not delete 'Begin/End Wizard Added Variable Initialization' lines **
		// Begin Wizard Added Variable Initialization

		this.edvJobOper = ((EpiDataView)(this.oTrans.EpiDataViews["JobOper"]));
		this.edvJobOper.EpiViewNotification += new EpiViewNotification(this.edvJobOper_EpiViewNotification);
		this.edvCallContextBpmData = ((EpiDataView)(this.oTrans.EpiDataViews["CallContextBpmData"]));
		this.edvCallContextBpmData.EpiViewNotification += new EpiViewNotification(this.edvCallContextBpmData_EpiViewNotification);
		// End Wizard Added Variable Initialization

		// Begin Wizard Added Custom Method Calls

		this.epiButtonC1.Click += new System.EventHandler(this.epiButtonC1_Click);
		// End Wizard Added Custom Method Calls
	}

	public void DestroyCustomCode()
	{
		// ** Wizard Insert Location - Do not delete 'Begin/End Wizard Added Object Disposal' lines **
		// Begin Wizard Added Object Disposal

		this.epiButtonC1.Click -= new System.EventHandler(this.epiButtonC1_Click);
		this.edvJobOper.EpiViewNotification -= new EpiViewNotification(this.edvJobOper_EpiViewNotification);
		this.edvJobOper = null;
		this.edvCallContextBpmData.EpiViewNotification -= new EpiViewNotification(this.edvCallContextBpmData_EpiViewNotification);
		this.edvCallContextBpmData = null;
		// End Wizard Added Object Disposal

		// Begin Custom Code Disposal

		// End Custom Code Disposal
	}

	private void epiButtonC1_Click(object sender, System.EventArgs args)
	{
		// ** Place Event Handling Code Here **
		
		
		edvCallContextBpmData.dataView[edvCallContextBpmData.Row]["Character01"] = "UPDATE ORDER";			
		edvJobOper.dataView[edvJobOper.Row]["RowMod"] = "U";
		oTrans.Update();
	}

	private void edvJobOper_EpiViewNotification(EpiDataView view, EpiNotifyArgs args)
	{
		// ** Argument Properties and Uses **
		// view.dataView[args.Row]["FieldName"]
		// args.Row, args.Column, args.Sender, args.NotifyType
		// NotifyType.Initialize, NotifyType.AddRow, NotifyType.DeleteRow, NotifyType.InitLastView, NotifyType.InitAndResetTreeNodes
		if ((args.NotifyType == EpiTransaction.NotifyType.AddRow))
		{
			if ((args.Row > -1))
			{
			}
		}
	}

	private void edvCallContextBpmData_EpiViewNotification(EpiDataView view, EpiNotifyArgs args)
	{
		// ** Argument Properties and Uses **
		// view.dataView[args.Row]["FieldName"]
		// args.Row, args.Column, args.Sender, args.NotifyType
		// NotifyType.Initialize, NotifyType.AddRow, NotifyType.DeleteRow, NotifyType.InitLastView, NotifyType.InitAndResetTreeNodes
		if ((args.NotifyType == EpiTransaction.NotifyType.AddRow))
		{
			if ((args.Row > -1))
			{
			}
		}
	}
}















































And for BPM I used this:

string msg ="Test";

string jobnum = "";
string company = "";
int ordernum = 0;
string CustName_c    ="";
string Address1_c    ="";
string Address2_c    ="";
string Address3_c    ="";
string City_c      ="";
string State_c      ="";
string PostalCode_c    ="";
string Country_c    ="";
string ContactPerson_c  ="";
string ContactPhone_c  ="";
string ContactEmail_c  ="";
string ShipVia_c    ="";
bool orderhedconfirmed = false;
bool orderrelconfirmed = false;
int orderrelnum = 0;
int orderlinenum = 0;


if(ttJobOper.Any())
{

this.PublishInfoMessage(msg,Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual,"","");
      foreach (var joboperrow in (from joboper_Row in ttJobOper
                                    //where  joboper_Row.Company == company && joboper_Row.OrderNum == ordernum
                                    select joboper_Row))
                                    {
                              
                                          //valueOTSCountryNum = countryrow.Description;
                                          jobnum          = joboperrow.JobNum       ;
                                          company         = joboperrow.Company      ;
                                          
                                          this.PublishInfoMessage("checkpoint01",Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual,"","");
                                          int jobnumlength = jobnum.Length;
                                          this.PublishInfoMessage("checkpoint02",Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual,"","");
                                          int orderlineindex = jobnum.IndexOf('-')+1;
                                          this.PublishInfoMessage("checkpoint03",Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual,"","");
                                          int orderrelindex  = jobnum.IndexOf('-', jobnum.IndexOf('-') + 1)+1;
                                          
                                          this.PublishInfoMessage(jobnum,Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual,"","");
                                          this.PublishInfoMessage(jobnumlength.ToString(),Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual,"","");
                                          this.PublishInfoMessage(orderlineindex.ToString(),Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual,"","");
                                          this.PublishInfoMessage(orderrelindex.ToString(),Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual,"","");
                                          
                                          string orderlineindexstring = jobnum.Substring(orderlineindex, orderlineindex-orderrelindex-2);
                                          string orderrelindexstring  = jobnum.Substring(orderrelindex, jobnumlength-1);
                                          
                                          this.PublishInfoMessage(orderlineindexstring,Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual,"","");
                                          this.PublishInfoMessage(orderrelindexstring,Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual,"","");
                                          
                                          string callcontext = callContextBpmData.Character01;
                                          string rowmod = joboperrow.RowMod;
                                          if(!rowmod.Equals("U") || !callcontext.Equals("UPDATE ORDER"))
                                          {
                                          }
                                          else
                                          {
                                                foreach (var jobprodrow in (from jobprod_Row in Db.JobProd
                                                      where  jobprod_Row.Company == company && jobprod_Row.JobNum == jobnum
                                                      select jobprod_Row))
                                                      {
                                                      ordernum          = jobprodrow.OrderNum       ;
                                                      }
                                          
                                          
                                          
                                                foreach (var orderhedrow in (from orderhed_Row in Db.OrderHed
                                                      where  orderhed_Row.Company == company && orderhed_Row.OrderNum == ordernum
                                                      select orderhed_Row))
                                                      {
                                                            foreach (var orderrelrow in (from orderrel_Row in Db.OrderRel
                                                                  where  orderrel_Row.Company == company && orderrel_Row.OrderNum == ordernum && orderrel_Row.OrderLine == orderlinenum && orderrel_Row.OrderRelNum == orderrelnum
                                                                  select orderrel_Row))
                                                                  {
                                                                        ordernum          = orderrelrow.OrderNum       ;            
                                                                  }
                                                            
                                                            //CustName_c      = orderhedrow.UDField<System.Boolean>("CustName_c");
                                                            if(true)
                                                            {
                                                            
                                                            
                                                            }
                                                            
                                                            CustName_c      = orderhedrow.UDField<System.String>("CustName_c");
                                                            Address1_c      = orderhedrow.UDField<System.String>("Address1_c");
                                                            Address2_c      = orderhedrow.UDField<System.String>("Address2_c");
                                                            Address3_c      = orderhedrow.UDField<System.String>("Address3_c");
                                                            City_c        = orderhedrow.UDField<System.String>("City_c");
                                                            State_c        = orderhedrow.UDField<System.String>("State_c");
                                                            PostalCode_c    = orderhedrow.UDField<System.String>("PostalCode_c");
                                                            Country_c      = orderhedrow.UDField<System.String>("Country_c");
                                                            ContactPerson_c    = orderhedrow.UDField<System.String>("ContactPerson_c");
                                                            ContactPhone_c    = orderhedrow.UDField<System.String>("ContactPhone_c");
                                                            ContactEmail_c    = orderhedrow.UDField<System.String>("ContactEmail_c");
                                                            ShipVia_c      = orderhedrow.UDField<System.String>("ShipVia_c");
                                                      }
                                                callContextBpmData.Character01 = "";                                      
                                          
                                          }
      
                                    }

}

I get this error:

Server Side Exception

BPM runtime caught an unexpected exception of 'ArgumentOutOfRangeException' type.
See more info in the Inner Exception section of Exception Details.

Exception caught in: Epicor.ServiceModel

Error Detail 
============
Correlation ID:  a9405720-6a2b-4994-ae57-32123d5b582c
Description:  BPM runtime caught an unexpected exception of 'ArgumentOutOfRangeException' type.
See more info in the Inner Exception section of Exception Details.
Program:  CommonLanguageRuntimeLibrary
Method:  Substring
Original Exception Type:  ArgumentOutOfRangeException
Framework Method:  A001_CustomCodeAction
Framework Line Number:  0
Framework Column Number:  0
Framework Source:  A001_CustomCodeAction at offset 1155 in file:line:column <filename unknown>:0:0


Client Stack Trace 
==================
   at Epicor.ServiceModel.Channels.ImplBase`1.ShouldRethrowNonRetryableException(Exception ex, DataSet[] dataSets)
   at Erp.Proxy.BO.JobEntryImpl.Update(JobEntryDataSet ds)
   at Erp.Adapters.JobEntryAdapter.OnUpdate()
   at Ice.Lib.Framework.EpiBaseAdapter.Update()
   at Erp.UI.App.JobEntry.Transaction.Update()

Inner Exception 
===============
Specified argument was out of the range of valid values.
Parameter name: Length cannot be less than zero.
Parameter name: length

The interesting fact is that not even the first part of BPM runs (the message just to see if it is entering the loop) so it seems something is wrong on Customization side. The customization compiles successfully, so I am not sure what could be causing this.
Thank you in advance

So I’m late to the game here (and admittedly didn’t read all of the posts). But if want to do some work on a button click, you could call a UBAQ on button click, and do any work you need there. You can do the look up with the BAQ if you want, or pass the info in via parameters from the customization. In the UBAQ you can do a “BPM” without having to hack a way to trick the system to run an actual BPM.

Just a thought.

Any reason to choose UBAQ over an Epicor Function? Other than Epicor Version of course…

1 Like

I was thinking version. And it’s easier (in classic anyways) to call a UBAQ than it is to call a function. In kinetic, that’s not the case, but until you get to app studio, calling functions are a bit of a pain. But yes, a function would work as well.

1 Like

I do not have functions… would the UBAQ be called using a BAQDV? Or something else? I’m not sure I follow… I like UBAQs… but I’ve never thought to use them this way.

If you search on how to call a BAQ from code (Jose has video examples) you can see how to call BAQ’s from code. It can be a BAQ or UBAQ. I believe there are some minor differences if I recall correctly, but it all uses the dynamicQueryAdapter. This was a way to make “Functions” before functions were an official thing.

We are just beginning our planning for our upgrade to Kinetic. I can’t wait to get my hands on functions. As for @Shizar115 , I can’t help you with this approach as I’ve yet to learn it myself. Your error is suggesting that there is an issue in the BPM itself… What do you have in your BPM?