List of UD03 entries from Customization to BPM Method Directive

Hello,

I am using UD03 to store employee work terms.
Key1 = employee ID
Key2 = Work term (1,2,3,…,n - keeping the UD03 rows unique for each entry)
Key3-5 are not being used.
Date01 = From Date
Date02 = To Date

In Employee Maintenance I have a table to show work terms as well as add/remove work terms.
image

To save these work terms to the UD03 table, initially I wanted to use an adapter in customization code, but it’s tough to find the right events. The ToolClick event for SaveTool only works when saving normally, not when exit with saving.

Therefore, I thought I’d use a Method Directive on EmpBasic.Update.
Since some situations would require to save a list of work terms, not just one work term, I can’t save them normally to the CallContextBPMData fields. I was thinking something like separating them using commas.

  • UD03.Key1 fields would be stored in callContextBPMData.Character01 like so:
    "11,11,11,11" - indicating the employee id
  • UD03.Key2 fields would be stored in Character02 like so:
    "1,2,4,7" - depending on which work term was modified or added.
  • UD03.Date01 field would be stored in Character03 like so:
    "2021-03-01,2022-02-01,2023-02-03,2024-01-30" - all start dates
  • UD03.Date02 field would be stored in Character04 like so:
    "2021-10-01,2022-10-22,2023-12-14,2024-11-28" - all end dates
  • I can also do something like json format, I just thought this might be easier to take apart inside the BPM.

This will allow me to split the Character01 - 04 strings from the callContextBPMData by comma and use split[0:3] to add to the UD03 table.

Is this a good approach? At first I didn’t want to use code because it’s prone to errors, but to save a list of entries like this, I can’t really think of another way.

Thank you!

Edit: Btw I just found out that string.split() is not even a method inside BPMs. I’ll have to go char by char I guess.

I do something like this and you could use the afterExitEditMode on the grid to put the data in to a json

private static void UpdateLots()
	{
		try
		{

			if (eugMaterial.Rows.Count == 0)
			return;
		}
		catch
		{
		}

		EpiDataView edvCallContextBpmData = ((EpiDataView)(Script.oTrans.EpiDataViews["CallContextBpmData"]));
		
		JArray items = new JArray() ; //"items";

		try
		{

			foreach (Infragistics.Win.UltraWinGrid.UltraGridRow gridRow in eugMaterial.Rows)
			{

				if ((gridRow.Cells["Calculated_LotNum"].Text.Trim() != "" && gridRow.Cells["RowMod"].Text.Trim() == "U" ))
				{

				string[] lotList = gridRow.Cells["Calculated_LotNum"].Text.Trim().Split(' ');
				foreach (string lotnum in lotList)
				{
					items.Add(new JObject()
					{
						{"part", gridRow.Cells["JobMtl_PartNum"].Text.Trim()},
						{"lot" , lotnum}
					});
				}	
				}
			}



			JObject collection = new JObject();
			collection.Add("items",JArray.FromObject(items));

			edvCallContextBpmData.dataView[0]["Character01"] = collection.ToString();

			var CCCData = oTrans.Factory("CallContextClientData");
			var user = CCCData.dataView[CCCData.Row]["CurrentUserId"].ToString();

			if (user == "gpay01")
			MessageBox.Show(" json \n" + collection.ToString());

			Ice.Lib.Framework.EventLog.Current.WriteEntry(collection.ToString());



		}

		catch (Exception e)
		{
			MessageBox.Show("Lot Update Error:" + "\r\n" + e.ToString());
		}
	}

Then pick that up in the bpm and write the data.

string lotString = callContextBpmData.Character01;
        
        JObject lots = JObject.Parse(callContextBpmData.Character01);
1 Like

Hi Greg, thank you very much for your answer! It’s exactly what I need and seems to solve it.

1 Like

I’d personally avoid concatenated strings whenever possible. Still, as a way to pass values to a BO or API, it sometimes is the only way to go.

If “work items” are children of the “work term” I’d look at the UD1xx tables. They let you define a parent record (e.g. UD100 table) and any number of child records (UD100A table). The flat, albeit multikey, structure of the normal UDxx tables can accommodate parent-child relationships, but it’s still a flat table.

I’d, personally, go that route and have a separate grids for terms and items.

I’d personally avoid concatenated strings whenever possible. Still, as a way to pass values to a BO or API, it sometimes is the only way to go.

Noted. I didn’t like this specific solution, but it was “a” solution :sweat_smile:

If “work items” are children of the “work term” I’d look at the UD1xx tables. They let you define a parent record (e.g. UD100 table) and any number of child records (UD100A table). The flat, albeit multikey, structure of the normal UDxx tables can accommodate parent-child relationships, but it’s still a flat table.

This is valuable information.
I am not using “work items” and, therefore, won’t be needing parent-child UD tables in this case. However, this is useful for the future as I’m sure I’ll need to do this at one point.

I appreciate the information!

For anyone interested, here’s my method directive:

// Post-Processing Method Directive on ERP.BO.EmpBasic.Update named "UpsertWorkTerms"
JObject items = JObject.Parse(callContextBpmData.Character01);

Ice.Tables.UD03 UD03;

// Delete all rows for user
foreach (var item in items["items"]) {
  string company = Session.CompanyID;
  string key1 = item["key1"].ToString();
  string key2 = item["key2"].ToString();
  using (var txScope = IceContext.CreateDefaultTransactionScope()) {
    var rowToDelete = Db.UD03.Where(r => r.Company == company && r.Key1 == key1 && r.Key2 == key2).FirstOrDefault();
    if (rowToDelete != null)
      Db.UD03.Delete(rowToDelete);
    Db.Validate();
    txScope.Complete();
  }
}

// Add all rows for user
using (var txScope = IceContext.CreateDefaultTransactionScope()) {
  foreach (var item in items["items"]) {
    if (String.Equals(item["delete"].ToString(), "FALSE")) {
      UD03 newRow = new UD03();
      Db.UD03.Insert(newRow);
      newRow.Company = Session.CompanyID;
      newRow.Key1 = item["key1"].ToString();
      newRow.Key2 = item["key2"].ToString();
      if (item["date01"].ToString().Length > 0)
        newRow.Date01 = DateTime.ParseExact(item["date01"].ToString(), "yyyy-MM-dd", CultureInfo.InvariantCulture);
      if (item["date02"].ToString().Length > 0)
        newRow.Date02 = DateTime.ParseExact(item["date02"].ToString(), "yyyy-MM-dd", CultureInfo.InvariantCulture);
      Db.Validate();
    }
  }
  txScope.Complete();
}

Storing in CallContextBPMData from customization code:

private void populateCallContextBPMData()
{
	JArray items = new JArray();
	for (int i = 0; i < this.epiWorkTermsGrid.Rows.Count; i++)
	{
		items.Add(new JObject() {
			{"key1", this.epiEmpIdBox.Text},
			{"key2", this.epiWorkTermsGrid.Rows[i].Cells["UD03_EmpBasic_WorkTerms_Key2"].Text},
			{"date01", this.epiWorkTermsGrid.Rows[i].Cells["UD03_EmpBasic_WorkTerms_Date01"].Text},
			{"date02", this.epiWorkTermsGrid.Rows[i].Cells["UD03_EmpBasic_WorkTerms_Date02"].Text},
			{"delete", "FALSE"}
		});
	}
	for (int i = 0; i < this.termRowsToDelete.Count; i++)
	{
		items.Add(new JObject() {
			{"key1", termRowsToDelete[i].key1},
			{"key2", termRowsToDelete[i].key2},
			{"date01", termRowsToDelete[i].date01},
			{"date02", termRowsToDelete[i].date02},
			{"delete", "TRUE"}
		});
	}
	JObject collection = new JObject();
	collection.Add("items", JArray.FromObject(items));
	// set bpm value
	var edvBPMData = this.oTrans.Factory("CallContextBpmData");
	var edvBPMDataRow = edvBPMData.CurrentDataRow;
	if (edvBPMDataRow != null)
		edvBPMDataRow["Character01"] = collection.ToString();
}

The populateCallContextBPMData() method is called every time the user exits edit mode in the table (AfterExitEditMode event handler) which takes care of when someone adds a row and changes the dates. The method is also called every time a row is deleted from the table using the (X) button.