Row Rule Help

Need some help with a Row Rule I am trying to do.

On the Work Queue, I want a row to be highlighted if it has an Inspection Plan on it.

I was able to use FKV off of the WorkQueue dataview to get the Job dataset and then did a STV to the JobOperInsp table. This is where I hit a dead end since my condition to highlight is (roughly):

if (WorkQueue.JobNum == JobOperInsp.JobNum &&
          WorkQueue.AssmSeq == JobOperInsp.AssmSeq &&
          WorkQueue.OpSeq == JobOperInsp.OpSeq)
{ highlight }
{ don't highlight }

The Row Rule Wizard does not seem like it can handle something like that so I tried doing my own code and it failed. Now I am thinking that I would like to use WorkQueue.CheckBox04 to indicate if there is a related row, but I am not sure what Event Type to use.

So, basically I am wondering what Event I should be using to fill the row with this data? Is it Retrieve, AfterAdapterMethod, or something else?

Or, am I going down the completely wrong path and someone has a much better way?

I may get yelled at for this, but a Data Directive on the WorkQueue table can set CheckBox04 for you. Please be aware that a Data Directive Always runs, so be sure to do your homework to write it well. I would only do your work on an Added row, FYI.

Then your RowRule can simply look at that field to highlight.

3 Likes

I won’t yell at you. But the views in the Work Queue are in memory so I can’t affect them with a DD. Unless I am mistaken :no_mouth:

You need to populate the field as it is created in the Database. Then when it is pulled into memory (ttWorkQueue), then it will have the value.

1 Like

Ah! I’m following you now. Let me see what I can do.

1 Like

That field does not exist on the table other than in the Work Queue screen. I could always add a field, but I would rather not as all of the data is there, just need to figure out the right way to get it.

I’m going to try using an adapter instead of creating the views.

If it is not there, then CheckBox04 is from a different table.
However, if that field is currently never populated (Although I would be surprised), you can use a Post-Processing Method Directive BPM when the screen loads the data.

2 Likes

@josecgomez @hkeric.wci @Chris_Conn or any other C# expert.

So, I got it working. And by working I mean it does what I want and then crashes out all of Epicor. I know it is because I did the NotifyType as Initialize so it is firing multiple times. Am I toast here and should give up? Or is there something I could do to make this usable?

// **************************************************
// Custom code for WorkQueueForm
// Created: 2/4/2021 2:06:55 PM
// **************************************************

extern alias Erp_Contracts_BO_WorkQueue;
extern alias Erp_Contracts_BO_Labor;
extern alias Erp_Contracts_BO_EmpBasic;
extern alias Erp_Contracts_BO_ResourceGroup;
extern alias Erp_Contracts_BO_Resource;

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 edvWorkQueue;
	// 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.edvWorkQueue = ((EpiDataView)(this.oTrans.EpiDataViews["WorkQueue"]));
		this.edvWorkQueue.EpiViewNotification += new EpiViewNotification(this.edvWorkQueue_EpiViewNotification);
		// End Wizard Added Variable Initialization

		// Begin Wizard Added Custom Method Calls

		// 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.edvWorkQueue.EpiViewNotification -= new EpiViewNotification(this.edvWorkQueue_EpiViewNotification);
		this.edvWorkQueue = null;
		// End Wizard Added Object Disposal

		// Begin Custom Code Disposal

		// End Custom Code Disposal
	}

	private void edvWorkQueue_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.Initialize))
		{
			if ((args.Row > -1))
			{
				foreach (DataRowView wqRow in edvWorkQueue.dataView)
				{
					string curJob = wqRow["JobNum"].ToString();
					int curAsm = Convert.ToInt32(wqRow["AssemblySeq"]);
					int curOp = Convert.ToInt32(wqRow["OprSeq"]);
					JobEntryAdapter jeAdapt = new JobEntryAdapter(oTrans);
					jeAdapt.BOConnect();
					jeAdapt.GetByID(wqRow["JobNum"].ToString());
					DataTable dtJOI = jeAdapt.JobEntryData.Tables["JobOperInsp"];
					foreach (DataRow drRow in dtJOI.AsEnumerable())
					{
						if (wqRow["Company"].ToString() == drRow["Company"].ToString() &&
							wqRow["JobNum"].ToString() == drRow["JobNum"].ToString() &&
							Convert.ToInt32(wqRow["AssemblySeq"]) == Convert.ToInt32(drRow["AssemblySeq"]) &&
							Convert.ToInt32(wqRow["OprSeq"]) == Convert.ToInt32(drRow["OprSeq"]))
						{
							wqRow["CheckBox04"] = true;
						}
					}
				}
			}
		}
	}
}

Yeah doing this in the notify is going to explode your Epicor. Your FKV approach was probably close to what you could do, you could but a Rule on that FKV and have it disable the other view via custom action?

Thanks. I’ll play around with the FKV again. I appreciate you reviewing.

Any reason you aren’t doing a BPM? Doing this in the Customization is going to be much slower. A FKV won’t help as it will only grab on e Job at a time.

2 Likes

Because I would have to create a UD field to hang off of the JobOper table and check to see if there is an Inspection on it. Then if someone deletes the Inspection, I will have to do a BPM to check for that.

If I can figure out how to do it with a Row Rule, then I don’t need to worry about if Inspections are added or removed, I will be checking on the Work Queue if there is a related record in JobOperInsp. To me, trying to make sure I have all of my bases covered through a BPM(s) seems like a difficult route to go.

1 Like

@hkeric.wci I am trying to use your example where you use the BOReader to check the Customer table but am getting an error with BOReaderImpl and BOReaderSvcContract not existing in Ice.Proxy.Lib and Ice.Contracts. Can you let me know what using and/or assembly I need?

Just add an assembly reference to BOReader BO

private void CreateRowRuleOrderHedConsInvoiceCustom()
{
	RuleAction[] ruleActions = new RuleAction[] {
		RuleAction.AddControlSettings(this.oTrans, "InvcHead.idxConsInv_c", SettingStyle.ReadOnly),
		RuleAction.SetColumnValue(this.oTrans, "InvcHead.idxConsInv_c", false)
	};

	RowRule rr = new RowRule("InvcHead.CustNum", new RowRuleConditionDelegate2(this.isCustomerAllowedForConsolidationInvoice), "InvcHead.idxConsInv_c", ruleActions);
	((EpiDataView)(this.oTrans.EpiDataViews["InvcHead"])).AddRowRule(rr);
}

private bool isCustomerAllowedForConsolidationInvoice(Ice.Lib.ExtendedProps.RowRuleDelegateArgs args)
{
	bool disable = true;

	string invType = args.Row["InvoiceType"].ToString();
	bool isCreditMemo = Convert.ToBoolean(args.Row["CreditMemo"]);
	bool isCorrectionInvoice = Convert.ToBoolean(args.Row["CorrectionInv"]);

	if ((invType != "SHP" && invType != "MIS") || isCreditMemo == true || isCorrectionInvoice)
	{
		return disable;
	}

	bool currentSetting = Convert.ToBoolean(args.Arg2);
	string whereClause = string.Format("CustNum = '{0}'", Convert.ToString(args.Arg1));

	using(Ice.Proxy.Lib.BOReaderImpl _bor = WCFServiceSupport.CreateImpl<Ice.Proxy.Lib.BOReaderImpl>((Ice.Core.Session)oTrans.Session, Epicor.ServiceModel.Channels.ImplBase<Ice.Contracts.BOReaderSvcContract>.UriPath))
	{
		System.Data.DataSet ds = _bor.GetList("Erp:BO:Customer", whereClause, "CustNum, idxConsInv_c");

		if (ds != null && ds.Tables[0].Rows.Count > 0) {
			disable = Convert.ToBoolean(ds.Tables["CustomerList"].Rows[0]["idxConsInv_c"]) == false;
		}
	}

	if (currentSetting && disable) {
		EpiMessageBox.Show(Script.GetStringByID("ConsolidatedInvoiceWarningMessage"),
			EpiString.GetString("Warning"), MessageBoxButtons.OK, MessageBoxIcon.Warning);
	}

	return disable;
}

I copied and pasted your code(the using part of the code) and it still is not working. Getting the same error. Any ideas?

private bool ActiveWorkCompanytrue_CustomRuleCondition(Ice.Lib.ExtendedProps.RowRuleDelegateArgs args)
	{
		bool result = false;
		string arComp = args.Row["Company"].ToString();
		string arJob = args.Row["JobNum"].ToString();
		int arAssm = Convert.ToInt32(args.Row["AssemblySeq"]);
		int arOp = Convert.ToInt32(args.Row["OprSeq"]);
		string whereClause = string.Format("Company = " + arComp + " AND JobNum = " + arJob + " AND AssemblySeq = " + arAssm + " AND OprSeq = " + arOp);
		using(Ice.Proxy.Lib.BOReaderImpl _bor = WCFServiceSupport.CreateImpl<Ice.Proxy.Lib.BOReaderImpl>((Ice.Core.Session)oTrans.Session, 
			Epicor.ServiceModel.Channels.ImplBase<Ice.Contracts.BOReaderSvcContract>.UriPath))
		{
			result = false;
		}
		return result;
	}

Make sure you add reference to the Assembly:

image

Then maybe you need a

using Ice.BO;
2 Likes

Most screens use a GetList or GetRows Method to bring data to the screen (use a Trace to see what Method is called). If you use a Post-Processing BPM on this, you can populate the CheckBox04 that already exists with whatever value you want. I’ve used this Hijack process many times to save myself a lot of code.

Then the row rule is very simple.

2 Likes

But the CheckBox04 only exists in the DataView. Also, the DataView does not mirror the database, they are all views that were constructed for that screen only.

Are you saying you can’t see it in the trace from the server?

Jason Woods
http://LinkedIn.com/in/jasoncwoods

1 Like

+1 @Jason_Woods - except I’d do this on a GetXXXXX Method not a Data Directive.
No UD fields need to be created to do this, we are messing with the DataTable/DataSet in memory and we just need to get the RowRule highlight in the client.
I have done something very similar for the Work Queue (and Time Phase) to avoid custom code that might break or have to rewrite later (like in the coming Kinetic).
In the Work Queue case they wanted to see something like days late or something.
I used the WorkQueue.GetOpsInResourceGroup Method Post Processing.
In that BPM it’s just the Update Table by Query widget and I query the Data I want per Job and then assign results to the existing ttWorkQueue Dataset.
In the data there are 5 off the following: Date, Number, ShortChar, Checkbox.
I set the appropriate Query Result values to the fields I wanted and in some cases used an Expression to get the value. Then the only thing I did was customize for the Row Rule evaluating those fields for the rule.


So being a Post Directive Method the TTWorkQueue data is being updated on it’s way back.
I really don’t like messing with FKV when they can be avoided, they are tricky as crap sometimes.

As for covering all your bases with BPM’s, if you have more than one trigger you need to account for (for WorkQueue I found WorkQueue.GetOpsInResourceGroup covered everything in Work Queue) you can instead setup a Function and then just have the right BPM’s call the Function, that way you only have one instance of code to mess with.

1 Like