Change Resource Group ID for Open Jobs

Hi Everyone!
I am working on a custom dashboard containing a rather simple BAQ.

The goal is to update the Resource Group IDs for all open jobs. For example I want to change any instance of resource group ID = “12VA” to “4A”.

To keep things flexible, my approach is to provide the user a small form requesting the old resource group ID, and the new resource group ID. After the user enters the old resource group ID, they will click a button, populating the grid with the jobs and operations that will be updated in the next step. (This is where I am stuck at.)

Once the user reviews the list of jobs and operations to be updated, they will click a button to update the actual fields in the tables. (This is my next step.)

I built the form using a customized dashboard with an empty tracker view. To this, I added the text boxes, buttons, and the grid.

The first button pulls the list of jobs/ops to update based on what the user enters in the textbox. The BAQ has a parameter “OldResGrpID” to filter the results on the ResourceGroupID field. I am attempting to set that parameter through this code. However, this returns an error (shown below).

private void epiButtonC1_Click(object sender, System.EventArgs args)
	{
	// ** Place Event Handling Code Here **
	DynamicQueryAdapter yourbaq = new DynamicQueryAdapter(oTrans);
	DataTable results;
	yourbaq.BOConnect();
	string baqname = "ChangeOpenJobResGrpIDs";
	Ice.BO.DynamicQueryDataSet dsQuery = yourbaq.DynamicQueryData;
	if (dsQuery.DynamicQuery.Rows.Count == 0)
	{
		Ice.BO.DynamicQueryDataSet dsQDesign = yourbaq.QueryDesignData;
		DataRow targetRow;
		foreach (DataTable table in dsQuery.Tables)
		{
			foreach (DataRow sourceRow in dsQDesign.Tables[table.ToString()].Rows)
			{
				targetRow = table.NewRow();
				targetRow.ItemArray = sourceRow.ItemArray;
				table.Rows.Add(targetRow);
			}
		}
	}
	Ice.BO.QueryExecutionDataSet dsBAQ = yourbaq.GetQueryExecutionParameters(dsQuery);
	dsBAQ.ExecutionParameter[0].ParameterID = "OldResGrpID";
	dsBAQ.ExecutionParameter[0].IsEmpty = false;
	dsBAQ.ExecutionParameter[0].ParameterValue = epiTextBoxC1.Value.ToString();
	dsBAQ.AcceptChanges();
	// Run the query using the users old resource group ID as the filter			
	yourbaq.Execute(dsQuery, dsBAQ);
	
	// Set results of query to the grid view
	epiUltraGridC1.DataSource=yourbaq.QueryResults.Tables["Results"].DefaultView;
	}

The error is:

Application Error

Exception caught in: Epicor.ServiceModel

Error Detail

Message: Can’t find query definition in passed dataset
Program: Epicor.ServiceModel.dll
Method: ShouldRethrowNonRetryableException

Client Stack Trace

at Epicor.ServiceModel.Channels.ImplBase`1.ShouldRethrowNonRetryableException(Exception ex, DataSet dataSets)
at Ice.Proxy.BO.DynamicQueryImpl.GetQueryExecutionParameters(DynamicQueryDataSet queryDS)
at Ice.Adapters.DynamicQueryAdapter.GetQueryExecutionParameters(DynamicQueryDataSet ds)
at Script.epiButtonC1_Click(Object sender, EventArgs args) in c:\ProgramData\Epicor\centralusdtpilot01.epicorsaas.com-443\3.2.400.0\VTAERO\CustomDLLs\App.ChangeResGrpIDs.MainController.EP.VTAERO.Customization.Customized1.CustomCode.7.cs:line 353
at System.Windows.Forms.Control.OnClick(EventArgs e)
at Infragistics.Win.Misc.UltraButtonBase.OnClick(EventArgs e)
at Ice.Lib.Framework.EpiButton.OnClick(EventArgs e)
at Infragistics.Win.Misc.UltraButton.OnMouseUp(MouseEventArgs e)
at Ice.Lib.Framework.EpiButton.OnMouseUp(MouseEventArgs e)
at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

Tracing with VS indicates the error is with this line:

Ice.BO.QueryExecutionDataSet dsBAQ = yourbaq.GetQueryExecutionParameters(dsQuery);

What am I missing with dsQuery? I am almost certain it is a picky syntax issue.
I am still working out the code to process the update. I will keep this thread updated until we come up with a solution. I am open to alternative suggestions for performing the required task.

Thanks for your time!
Nate

I found a slightly modified version of this code that seems to work well. I found it here:

private void epiButtonC1_Click(object sender, System.EventArgs args)
	{
	// ** Place Event Handling Code Here **
	
	DynamicQueryAdapter yourbaq = new DynamicQueryAdapter(this.oTrans);
	DataTable results;
	yourbaq.BOConnect();
	string baqname = "ChangeOpenJobResGrpIDs";
	Ice.BO.QueryExecutionDataSet dsBAQ = yourbaq.GetQueryExecutionParametersByID(baqname);
	dsBAQ.ExecutionParameter[0].ParameterID = "OldResGrpID";
	dsBAQ.ExecutionParameter[0].IsEmpty = false;
	dsBAQ.ExecutionParameter[0].ParameterValue = epiTextBoxC1.Value.ToString();
	dsBAQ.AcceptChanges();
	yourbaq.ExecuteByID(baqname, dsBAQ);
	
	// Set results of query to the grid view
	epiUltraGridC1.DataSource=yourbaq.QueryResults.Tables["Results"].DefaultView;
	}

This works great so far. Thanks Daryl! I will get on the update portion next.

I’m back for more fun!

Taking steps towards my goal of updating the Resource Group IDs for open jobs, I have done the following:

  1. Created an updatable BAQ based on the Job tables. The query returns the JobNum, AssemblySeq, OpSeq, OpDescription, ResourceGroupID, and ResourceGroupDescription. The updatable fields are set to the last three of these fields.

  2. Created custom BPM to handle the updating.


    Here I set the internal variables, NewResGrpID, and NewResGrpDesc to the callContextBPMData elements, .ShortChar01 and .Character01, respectively. I’m trying to populates these later through the form customization.

This is the custom code in the BPM. The goal here is to pull the internal variables into the uBAQ update fields.

foreach (var xRow in (from ttResults_Row in ttResults select ttResults_Row))
{
xRow.JobOpDtl_ResourceGrpID = NewResGrpID;
xRow.JobOper_OpDesc = NewResGrpDesc;
xRow.JobOpDtl_OpDtlDesc = NewResGrpDesc;
}
  1. Created a dashboard to house the query and the customized form. The form contains three textboxes, and two buttons as follows:
    epiTextBoxC1 = OldResGrpID
    epiTextBoxC2 = NewResGrpID
    epiTextBoxC3 = NewResGrpDesc
    epiButtonC1 = Get open jobs using old resource group id and populate grid
    epiButtonC2 = Change resource group IDs and descriptions

I have the code worked out for the first button (see previous posts). Now I am working out the code to get the second button working. This is what I have so far:

private void epiButtonC2_Click(object sender, System.EventArgs args)
	{
		// ** Place Event Handling Code Here **
		// Make sure New ID and Description text fields are populated
		if (epiTextBoxC2.Value.ToString() == "")
		{
			 MessageBox.Show("Please enter a new Resource Group ID.");
			 return;
		}
		if (epiTextBoxC3.Value.ToString() == "")
		{
			 MessageBox.Show("Please enter a new Resource Group Description.");
			 return;
		}
		// set callcontextbpmdata fields to textbox values
		using (var adapter = new JobEntryAdapter(oTrans.PrimaryAdapter))
		{ 
		adapter.BOConnect();
		adapter.BpmContext = new ContextDataSet();
		var row = adapter.BpmContext.BpmData.NewRow();
		row["ShortChar01"] = epiTextBoxC2.Value.ToString();
		row["Character01"] = epiTextBoxC3.Value.ToString();
		adapter.BpmContext.BpmData.Rows.Add(row);		
		}

		//? Do I need to close or otherwise deactivate the job before making operation changes?
		// Run custom code to change resource group ID for open jobs from old to new res grp id
		// Run custom code to update op description and res grp description to new description
		// Run code to update entire record and validate changes 
		DynamicQueryAdapter dqa = new DynamicQueryAdapter(oTrans);
		DataTable results;		
		results = new DataTable(); 
		dqa.BOConnect();
		dqa.ExecuteByID("ChangeOpenJobResGrpIDs");		
		Ice.BO.DynamicQueryDataSet dqDS;
		dqDS = dqa.DynamicQueryData;
		dqa.Update(dqDS, results.DataSet);
		dqa.RunCustomAction(dqDS, "ChangeResGrpID", results.DataSet, true);
                epiUltraGridC1.DataSource=dqa.QueryResults.Tables["Results"].DefaultView;	}

The goal in this code is to first check that the textboxes are filled in. Then we try to take the data from the textboxes and assign it to the callcontextbpmdata elements. This is so that when the custom action runs, the callcontextbpmdata elements are not blank.

This code all compiles ok, but when I run it, I get the following error:

Application Error

Exception caught in: Ice.Lib.EpiClientLib

Error Detail

Message: Invalid request – object Sender must be Core.Session or implement ILaunch
Program: Ice.Lib.EpiClientLib.dll
Method: GetSessionFromSender

Client Stack Trace

at Ice.Lib.Framework.FormFunctions.GetSessionFromSender(Object sender)
at Ice.Lib.Framework.EpiBaseAdapter.get_Session()
at Ice.Lib.Framework.EpiBaseAdapter.BOConnect()
at Script.epiButtonC2_Click(Object sender, EventArgs args)
at System.Windows.Forms.Control.OnClick(EventArgs e)
at Infragistics.Win.Misc.UltraButtonBase.OnClick(EventArgs e)
at Ice.Lib.Framework.EpiButton.OnClick(EventArgs e)
at Infragistics.Win.Misc.UltraButton.OnMouseUp(MouseEventArgs e)
at Ice.Lib.Framework.EpiButton.OnMouseUp(MouseEventArgs e)
at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

Does this approach make sense? Is there a better way to get the values from the textboxes into the designated update fields in the uBAQ?

Thanks again for your time! I appreciate your help!
Nate

My last questions still apply, but for what it’s worth, I traced the error in VS. The problem is with the line:

adapter.BOConnect();

So I swapped out a bit of code. This little bit seems to work to seet the callcontextbpmdata fields.

		// set callcontextbpmdata fields to textbox values
		EpiDataView edvCallContextBpmData = ((EpiDataView)(this.oTrans.EpiDataViews["CallContextBpmData"]));
		System.Data.DataRow edvCallContextBpmDataRow = edvCallContextBpmData.CurrentDataRow;
		edvCallContextBpmDataRow["ShortChar01"] = epiTextBoxC2.Value.ToString();
		edvCallContextBpmDataRow["Character01"] = epiTextBoxC3.Value.ToString();

Now the error is farther down. I have seen this before, and I am sure Chris and Daryl will recognize it from my previous posts.

Can’t find query definition in passed dataset.

This occurs when I try to run the custom action in the BAQ. Here is my updated code for the second button.

private void epiButtonC2_Click(object sender, System.EventArgs args)
	{
		// ** Place Event Handling Code Here **
		// Make sure New ID and Description text fields are populated
		if (epiTextBoxC2.Value.ToString() == "")
		{
			 MessageBox.Show("Please enter a new Resource Group ID.");
			 return;
		}
		if (epiTextBoxC3.Value.ToString() == "")
		{
			 MessageBox.Show("Please enter a new Resource Group Description.");
			 return;
		}
		// set callcontextbpmdata fields to textbox values
		EpiDataView edvCallContextBpmData = ((EpiDataView)(this.oTrans.EpiDataViews["CallContextBpmData"]));
		System.Data.DataRow edvCallContextBpmDataRow = edvCallContextBpmData.CurrentDataRow;
		edvCallContextBpmDataRow["ShortChar01"] = epiTextBoxC2.Value.ToString();
		edvCallContextBpmDataRow["Character01"] = epiTextBoxC3.Value.ToString();
		//? Do I need to close or otherwise deactivate the job before making operation changes?
		// Run custom code to change resource group ID for open jobs from old to new res grp id
		// Run custom code to update op description and res grp description to new description
		// Run code to update entire record and validate changes 
		DynamicQueryAdapter dqa = new DynamicQueryAdapter(this.oTrans);
		DataTable results;		
		results = new DataTable();
 	   dqa.BOConnect();       
 	   string baqname = "ChangeOpenJobResGrpIDs";
		Ice.BO.DynamicQueryDataSet dqDS = dqa.DynamicQueryData;
		dqa.RunCustomAction(dqDS, "ChangeResGrpID", results.DataSet, true);
		dqa.Update(dqDS, results.DataSet);		
		LoadMyBAQ("ChangeOpenJobResGrpIDs", epiUltraGridC1);
	}

This is similar to the error I am still encountering from one of my lasts posts:

For some reason .Update and .RunCustomAction calls are failing with the same error. My dataset is not properly defined, and I am not clear on why.

I am still trying to figure out the solution to both problems. A good solution here could kill both of these posts! Thanks!
Nate

Thanks to Jose Gomez for helping out over on the other post. I can now run custom actions through my customization code!

Now onto the final hurdle! I need to get my custom action working properly. As I said above, the goal of this custom code is to change the Resource Group ID and Description fields to the values specified by the user. These values get passed along to the BAQ though call context BPM data elements. The values get passed along alright, but the custom action doesn’t do what I want.

So I have modified the code a bit.

foreach (var xRow in (from ttResults_Row in ttResults select ttResults_Row))
{
  MyJob = xRow.JobHead_JobNum;
  MyAsm = xRow.JobAsmbl_AssemblySeq;
  MyOp = xRow.JobOper_OprSeq;
  
  Erp.Tables.JobOpDtl JobOpDtl;
  Erp.Tables.JobOper JobOper;
  using (var txscope1 = IceDataContext.CreateDefaultTransactionScope())
   {
     var op = (from opRow in Db.JobOpDtl where opRow.JobNum == MyJob && opRow.AssemblySeq == MyAsm && opRow.OprSeq == MyOp select opRow);
     if (op != null)
        {    
          op.ResourceGrpID = NewResGrpID;
          op.OpDtlDesc = NewResGrpDesc;
          Db.Validate(op);
        }
     var oper = (from operRow in Db.JobOper where operRow.JobNum == MyJob && operRow.AssemblySeq == MyAsm && operRow.OprSeq == MyOp select operRow);
     if (oper != null)
      {
        oper.OpDesc = NewResGrpDesc;
        Db.Validate(oper);
      }
      txscope1.Complete();
   }
}

This won’t compile and returns:

‘IQueryable’ does not contain a definition for ‘ResourceGrpID’ and no accessible extension method ‘ResourceGrpID’ accepting a first argument of type ‘IQueryable’ could be found (are you missing a using directive or an assembly reference?)

‘IQueryable’ does not contain a definition for ‘OpDtlDesc’ and no accessible extension method ‘OpDtlDesc’ accepting a first argument of type ‘IQueryable’ could be found (are you missing a using directive or an assembly reference?)

The type ‘System.Linq.IQueryable<Erp.Tables.JobOpDtl>’ cannot be used as type parameter ‘TLinqRow’ in the generic type or method ‘IceDataContext.Validate(TLinqRow)’. There is no implicit reference conversion from ‘System.Linq.IQueryable<Erp.Tables.JobOpDtl>’ to ‘Ice.LinqRow’.

‘IQueryable’ does not contain a definition for ‘OpDesc’ and no accessible extension method ‘OpDesc’ accepting a first argument of type ‘IQueryable’ could be found (are you missing a using directive or an assembly reference?)

The type ‘System.Linq.IQueryable<Erp.Tables.JobOper>’ cannot be used as type parameter ‘TLinqRow’ in the generic type or method ‘IceDataContext.Validate(TLinqRow)’. There is no implicit reference conversion from ‘System.Linq.IQueryable<Erp.Tables.JobOper>’ to ‘Ice.LinqRow’.

Here is my interpretation of what should be happening:

  1. The foreach statement loops through all the records in the query.
  2. For each record in the query we pull down the current jobnum, asmseq, and opseq. This should be enough to identify the records in the underlying tables.
  3. Next we open up the JobOpDtl table and try to update the ResourceGrpID, and OpDtlDesc for any record that matches the current record from the foreach loop.
  4. Next we open up the JobOper table and try to update the OpDesc for any record that matches the current records from the foreach loop.
  5. Finally, we close the transaction, having made the appropriate updates.

Am I on the right track here? Am I way off?
Thanks so much for taking time to read and consider a solution!
Nate

I have considered the issue overnight. I believe that the op and oper variables are not being set correctly. I think that I need to alter the SQL statements to join in the jobhead, jobasmbl, joboper, and jobopdtl tables.

I am a bit fuzzy on my SQL and this is a strange dialect for me. I will keep trying to brute force the syntax. :hammer:

I have a few questions.

Is this new to you? (i don’t want to assume and walk you through something you may have done already)
The two fields NewResGrpID and NewResGrpDesc are they for each line, or is this a mass update?
Where are the two fields on the user form?

This is all a bit new to me, though I have been programming various languages for years. Adapting them all to Epicor has been tough. You all have been a great help!

The ID and description are set at the form level. The user types in a value for both fields (eventually drop down boxes). Then any open job that has ‘12VA’ as a resource group ID, for any operation, will be replaced with ‘4A’.

for testing I have disconnected the callcontextbpmdata elements and I am just working on the BPM code. So for now I just hard coded in the two values. But in the final run they will be passed in the BPM using the two set argument elements. That code will eventually be:

callContextBpmData.ShortChar01
//and
callContextBpmData.Character01

Does that make sense?

So the BAQ brings back all jobs that need to be updated.
User updates a few fields on the form.
Clicks Save

BPM fires
You want to find each of the jobs from the query and update the two fields.

The path you are going you will bypass the BO logic of Epicor? As the code loops through each record of the BAQ and tries to update the database directly with the field data from the form.

Yes that about sums it up. The only reason I am trying to bypass the BOs is because they didn’t work. See this old thread:

I get that same error when I try to use the JobEntry method ChangeJobOpDtlResourceGrpID.

Based on the information in the last post I think I am up against the same internal bug as Matthew. I have updated my code as follows and I get the same problem he did. I also commented out the ChangeJobOpDtlResourceGrpID method and tried to manually update the fields. That gave me a list of interesting errors like:

Cannot manually update the Production Labor Rate in JobOper.
Cannot manually update the Production Burden Rate in JobOper.
Cannot manually update the Setup Labor Rate in JobOper.
Cannot manually update the Setup Burden Rate in JobOper.

This custom code produced the errors above:

Erp.Contracts.JobEntrySvcContract jobEntryBO = null;
jobEntryBO = Ice.Assemblies.ServiceRenderer.GetService<Erp.Contracts.JobEntrySvcContract>(Db);

foreach (var row in ttResults)
{
  {
    myCount = myCount + 1;
    MyJob = row.JobHead_JobNum;
    MyAsm = row.JobAsmbl_AssemblySeq;
    MyOp = row.JobOper_OprSeq;
    var jobTableset = new JobEntryTableset();
    jobTableset = jobEntryBO.GetByID(row.JobHead_JobNum);
     
    var jobOpDtl = jobTableset.JobOpDtl;
    var jobOper = jobTableset.JobOper;
  
    var jobOpDtlRow = (from dtlRow in jobOpDtl where (dtlRow.Company == Session.CompanyID) && (dtlRow.AssemblySeq == row.JobAsmbl_AssemblySeq) && (dtlRow.OprSeq == row.JobOper_OprSeq) select dtlRow).FirstOrDefault();
    
    var jobOperRow = (from dtlRow in jobOper where (dtlRow.Company == Session.CompanyID) && (dtlRow.AssemblySeq == row.JobAsmbl_AssemblySeq) && (dtlRow.OprSeq == row.JobOper_OprSeq) select dtlRow).FirstOrDefault();
    
    jobOperRow.RowMod = "U";
    jobOperRow.OpDesc = NewResGrpDesc;
  
    jobOpDtlRow.RowMod = "U";
    jobOpDtlRow.ResourceGrpID = NewResGrpID;
    jobOpDtlRow.OpDtlDesc = NewResGrpDesc;
   // jobEntryBO.ChangeJobOpDtlResourceGrpID(NewResGrpID, ref jobTableset);
    
    jobEntryBO.Update(ref jobTableset);
    
  }
}

Well, the BO doesn’t work the way we want it to. There’s probably a good reason why the Epicor BO is not allowing a direct update of the Resource. Epicor may be making changes in other places of the database that you are not aware of. Maybe Capacity Planning? Costing? Capability Planning? We just don’t know. That’s why it’s best to work through the BOs the way the Client would and then automate from there instead of trying to get around it.

Have you run a trace of how to change a Resource Group on an Operation from the client?

I was afraid of that. That is a good reason.
I guess I just have to wait until they fix the BO?

I ran my trace on the JobEntry form. I manually found a job with ‘12VA’ an then started the trace, updated the code to ‘4A’, and then read the trace.

I assume you don’t want 15,000 lines of trace data. So the potentially relevant methods from my trace are:
Update
ChangeJobOpDtlResourceGrpID
CheckInactiveSchedRequirement
GetDatasetForTree (this probably isn’t relevant)

These types of methods are usually checks between lookup and the update. Sometimes they just check for errors, sometimes they do the change in your dataset (so you don’t have to in code).

I’m assuming that your list isn’t in order?

1 Like

The exact order of JobEntry BO methods are:

  • ChangeJobOpDtlResourceGrpID
  • CheckInactiveSchedRequirement
  • Update
  • GetDatasetForTree

I would rerun that trace, but start with a blank screen. Have all of your lookup values handy so you can typed them in instead of searching for them (because you won’t need the search stuff) But you will need a dataset populated, and your list methods doesn’t have anything that does that. So that dataset was populated before you started the trace.

That’s where Jose and Josh’s Trace Helper Utility comes into play!

It should help you identify only the methods that you’ll need to replicate the steps you want to do.

3 Likes

I would create a BPM

Use the BO widgets
GetJobByID
Change resource id field
ChangeJobOpDtlResourceGrpID
Update the RowMod
Update

What @Mark_Wonsil said!!

Yes! I got that utility this morning and I already love it! That is actually how I am getting the list of methods to use. I will try to use all BO widgets instead of doing it all in code.

I did modify the code to try to use just the methods:

Erp.Contracts.JobEntrySvcContract jobEntryBO = null;
jobEntryBO = Ice.Assemblies.ServiceRenderer.GetService<Erp.Contracts.JobEntrySvcContract>(Db);

foreach (var row in ttResults)
{
  {
    myCount = myCount + 1;
    MyJob = row.JobHead_JobNum;
    MyAsm = row.JobAsmbl_AssemblySeq;
    MyOp = row.JobOper_OprSeq;
    var jobTableset = new JobEntryTableset();
    jobTableset = jobEntryBO.GetByID(row.JobHead_JobNum);
     
   
    jobEntryBO.ChangeJobOpDtlResourceGrpID(NewResGrpID, ref jobTableset);
    jobEntryBO.CheckInactiveSchedRequirement("", "", NewResGrpID, out myString);
    jobEntryBO.Update(ref jobTableset);
    
  }
}

This returns a new error:

Business Layer Exception

Operation Detail is not available.

Exception caught in: Epicor.ServiceModel

Error Detail

Description: Operation Detail is not available.
Program: Erp.Services.BO.JobEntry.dll
Method: ChangeJobOpDtlResourceGrpID
Line Number: 11770
Column Number: 17
Table: JobOpDtl

Thanks all for your time and valuable feedback! I’ll keep trying!

1 Like