BPM Message to Include multiple PartNums and corresponding data from UDCodes

I’m hoping this may be a softball to those who are practiced in BPM’s and C#… I’m not.

I have a relatively simple BPM and it all works. However, the end result I’m looking for is to pop up a message with (hopefully) pertinent information, but I’m not sure how to get to it without code, nor how to write said code.

Business Case:
We have some parts for which we have internal specification documents which the Buyer needs to send along with the PO. We’re not set-up to auto-email Vendors because I want the buyers on the hook for reviewing everything and purposefully hitting “send”. So, I don’t need to do this via APR to attach a spec document or anything (although I may do that down the road).

What I’m looking for is just an “alert” message saying: “Hey one (or more) of the parts on this PO has an assigned specification, so pull that document and send it along with the PO.”

We store our Specification cross references in our UD02 table. So, Key1 is PartNum, Key2 is “SPEC” (along with other various possible values)… and if there is a row for a PartNum where Key2 is “SPEC”, Key5 holds a value of a UDCode (SPEC001 for example).

Below is the BPM as it stands… again, works. I can get a “warning” message to pop-up when there are (1) or more parts on a PO that have specifications against them.

Here’s the Query in that final Condition:
image

Again, I probably don’t NEED Ice.UDCodes even referenced in this query, because queries in conditions don’t return data (or if they do, I don’t know how to access it). I’m just using it for a row count.

What I’d LIKE to do is have the warning message specify WHICH part on the PO has a specification, as well as what that specification code is (CodeID), and potentially even the Description (CodeDesc).

So… I’m assuming a Code block for each PODetail Row where PartNum has a matching row on UD02 (where PartNum = Key1 and Key2 = “SPEC”)… and then I can get to the UDCode data where the UD02.Key5 = UDCode.CodeID… return CodeID and CodeDesc.

There potentially COULD be a part that has multiple specifications… so in that case, I wouldn’t mind seeing both values returned.

I’m assuming I can potentially inject a Code widget after the final condition and before the Message that would populate some kind of temp table?? But can I then reference those results in the final Message show?

I am going to be lazy and just give a general response, it very well may be all you need.

var targetParts = ds.PODetail.Where(w => w.MyField == "my condition"); //as many as needed
var MyPartsList = targetParts.Select(s => s.PartNum).ToList();
//have a string VARIABLE created on the BPM called whatever you want, MyParts as an example
MyParts = string.Join(",", MyPartList);

In your message, reference scalar variable MyParts.

3 Likes

I don’t mind “lazy”… I just need some guidance. I’ll see how far I can take this!

Thank you!

1 Like

Good luck, if you run into any specific issues ping me.

Also, just in case you dont know you can do multiple conditions using && ie.

ds.PODetail.Where(w => w. Company == “MyComp” && w.ClassID == “MyClass” && w.RunOut == true)

David, here is a code example I put together for a training once, you may find helpful.

As you can see there is just so much power and flexibility doing things through code and avoiding widgets… just don’t tell @JasonMcD :rofl:

foreach (var changed_OrderHedRow in ds.OrderHed.Where(oh_c => oh_c.Updated() || oh_c.Added()))
{
	//here is a way to implement a "field has changed" condition:  
	bool updated_cb02 = changed_OrderHedRow.UDField<bool>("CheckBox02");
	bool original_cb02 = ds.OrderHed.Where(oh_o => oh_o.Unchanged()
						&& oh_o.Company == changed_OrderHedRow.Company
						&& oh_o.SysRowID == changed_OrderHedRow.SysRowID)
						.Select(oh_o => oh_o.UDField<bool>("CheckBox02")).FirstOrDefault();

	if (original_cb02 == updated_cb02) // you can put more logic in here, perhaps specify if the field changes to a particular value
	{
		return; // BPM code exits here, so below code only fires if it gets past this condition
	}


	// now let's lookup data that is NOT in the bpm's dataset -- we use the Db context with LINQ statements to do this
	// if the information I ultimately need is a yes or no answer -- e.g. is the customer on credit hold? -- then I use the powerful .Any operator instead of pulling data into local variables 
	bool custCreditHold = Db.Customer.Any(cust => 
					cust.Company == changed_OrderHedRow.Company 
					&& cust.CustNum == changed_OrderHedRow.CustNum 
					&& cust.CreditHold);


	// now display a message depending on the information I just pulled
	if (custCreditHold)
	{
		string s1 = "Customer is on Credit Hold.";
    	this.PublishInfoMessage(s1,
            Ice.Common.BusinessObjectMessageType.Warning, // this setting makes it a warning box (.Information is for normal info message).
            Ice.Bpm.InfoMessageDisplayMode.Individual,
            String.Empty,
            String.Empty);
	}
}
3 Likes

Ha, all good.

Well even if I ceded that (I won’t), can @dcamlin do code on SaaS?

As far as I know most (or all?) of the SaaS BPM code restrictions (compared to on prem) are gone. @klincecum could probably tell us

3 Likes

Yes, except for the few remaining MT customers, I believe they still have a smattering.

1 Like

Okay… found some time to revisit and work on this.

@Chris_Conn… you may regret that!

Below is my current (simple) code:

foreach (var targetPart in tempPO.PODetail.Where(p => p.PONUM == intPONum))
{
  var specs = Db.UD02.Where(u => u.Key1 == targetPart.PartNum && u.Key2 == "SPEC").ToList();
  
  foreach (var targetCode in specs)
  {
    var code = Db.UDCodes.Where(c => c.CodeID == targetCode.Key5).ToList();
          
    MyParts += string.Join(",", specs.Select(s => s.Key1));
    Codes += string.Join(",", code.Select(c => c.CodeID));
    CodeDescs += string.Join(",", code.Select(c => c.CodeDesc));
  }
}

I set up my “Show Message” widget to display (3) separate lists (in order to test and make sure data was being returned:
image

My results look like:

part1part2
code1code2
description1description2

So, the string.Join() doesn’t seem to be comma delimiting anything.

BUT, I can at least verify I’m getting the data returned (and that’s huge for a “no-coder” such as myself). What would be GREAT, would be if I can switch this from independent lists to more of a datatable return:

Part Code Description
part1 code1 description1
part2 code2 description2

Or even independent records:

part1
code1
description1

part2
code2
description2

I tried to change the code to create a dataset… but (1) I don’t even know if this works… and (2) if it does, I don’t know how to push it into the warning message.

*NOTE: Attempting this, I started getting the warning that “These warnings will become errors in the future.”

Call System.IDisposable.Dispose on object created by ‘new DataSet()’ before all references to it are out of scope


DataSet dataSet = new DataSet();

DataTable partsTable = new DataTable("Parts");
partsTable.Columns.Add("PartNum", typeof(string));
partsTable.Columns.Add("CodeID", typeof(string));
partsTable.Columns.Add("CodeDesc", typeof(string));

dataSet.Tables.Add(partsTable);

foreach (var targetPart in tempPO.PODetail.Where(p => p.PONUM == intPONum))
{
  var specs = Db.UD02.Where(u => u.Key1 == targetPart.PartNum && u.Key2 == "SPEC").ToList();
  
  foreach (var targetCode in specs)
  {
    var code = Db.UDCodes.Where(c => c.CodeID == targetCode.Key5).ToList();
    
    foreach (var warn in code)
    {
      DataRow row = partsTable.NewRow();
      row["PartNum"] = targetPart.PartNum; // Or any other identifier for the part
      row["Spec"] = warn.CodeID;
      row["Description"] = warn.CodeDesc;

      partsTable.Rows.Add(row);
    }
  }
}

foreach (DataRow dataRow in partsTable.Rows)
{
    Console.WriteLine($"{dataRow["PartNum"]}, {dataRow["Spec"]}, {dataRow["Description"]}");

}

Any thoughts on if this can be accomplished?

I believe it just wants you to dispose the dataset you created before you leave scope. dataset.Dispose() at the end.

So if your table is getting the expected values that is great. To add to a warning I would use (in your foreach).

this.PublishInfoMessage($"{dataRow["PartNum"]} {dataRow["Spec"]} {dataRow["Description"]}", Ice.Common.BusinessObjectMessageType.Warning, Ice.Bpm.InfoMessageDisplayMode.Grid, "MyBPM", "MyId");

This would create a single pop up warning with lines for each record.

2 Likes

Well, that compiled fine… but I got the dreaded:

image

Maybe some day they’ll give Cloud users access to this… some day.

Like, how about today?

You can download the logs from Server File Download.

3 Likes

You did the dispose at very end? You could also do it by saying:

using(var dataset = new DataSet())
{
//all of your code here
}

Also, if you added the message code in too, comment it out to narrow down the issue.

2 Likes

Ack! I always forget about that.

Am I blind, because I don’t see you creating a DataSet in any of that code.

Edit: CTRL-F for the win.

season 1 friends GIF

2 Likes

Okay… working…but only getting one line of results.

image

Should be two rows.

Show your last code used, because I’m lost.

DataSet dataSet = new DataSet();

DataTable partsTable = new DataTable("Parts");
partsTable.Columns.Add("PartNum", typeof(string));
partsTable.Columns.Add("Spec", typeof(string));
partsTable.Columns.Add("Description", typeof(string));

dataSet.Tables.Add(partsTable);

foreach (var targetPart in tempPO.PODetail.Where(p => p.PONUM == intPONum))
{
  var specs = Db.UD02.Where(u => u.Key1 == targetPart.PartNum && u.Key2 == "SPEC").ToList();
  
  foreach (var targetCode in specs)
  {
    var code = Db.UDCodes.Where(c => c.CodeID == targetCode.Key5).ToList();
    
    foreach (var warn in code)
    {
      DataRow row = partsTable.NewRow();
      row["PartNum"] = targetPart.PartNum; 
      row["Spec"] = warn.CodeID;
      row["Description"] = warn.CodeDesc;

      partsTable.Rows.Add(row);
    }
  }
}

foreach (DataRow dataRow in partsTable.Rows)
{
   
    this.PublishInfoMessage($"{dataRow["PartNum"]} {dataRow["Spec"]} {dataRow["Description"]}", 
    Ice.Common.BusinessObjectMessageType.Warning, Ice.Bpm.InfoMessageDisplayMode.Grid, "Specification Alert", "dcamlin");

}

dataSet.Dispose();

This is known to be broken, and I’m not sure they intend to fix it.

2 Likes

Any known alternatives?

1 Like