Showing UD Field values from JobHead in Job Entry LandingPage View

I have an application studio customization layer that has an event listener on form load. Without describing everything because it works, I need to know how to keep my column updates in the grid from triggering a dirt row and setting the RowMod to U. When the form loads, the fields I want will update, but if I navigate away to home or whatever, it prompts me if I want to save my changes. I don’t want it trigger this dialogue!

Please help!

I think you’re gonna have to describe further.

What are these column updates and why are you doing them?

It may matter what the answers are.

Hi Kevin,

It would seem there is a bug.
The LandingPage view for Job Entry has options to include added UD fields in the personalization settings. The problem is that the UD Fields aren’t pulling into the view so they aren’t populating. The client wants to see their personal settings in the job selection screen. They are complaining that the checkboxes aren’t showing their selections when they add them as a personalization. I added the columns to the View in Application studio, and then I added an event listener for each row that looks up the values and sets them in the view as each row loads. It works great! the problem however is when I try to leave the Job Entry landing page, it prompts me if I want to save my changes. This will obviously be confuing for the user. I tried forcing rowmod to an empty value, but that triggers a dirty row as well.
This is just display data. there’s nothing to update.

Are these UD fields already in the dataset that populates the grid? And just not shown?

Is this what you want ?

I did this server side.

I can get the columns to show. I need the values to pull in. Are you able to see the values? They’re blank in your screen capture.

Are you willing to share what you did?

I’ll go look, those fields are used for overrides in mine and seldom used. Thought about that after.

If it works, of course :slight_smile:

Let me check it.

I will say though. if you were able to add those in a bpm to show in the landingpage view, I will actually be able to use that anyway. So if I can see your code on that, it will help even if they aren’t populated.

2 Likes

I lied (accidentally). They were used. And it didn’t work.
But now it does.

I’ll clean it up and post it tomorrow.

(Yes I know “Roll Label Qty” is on there twice. Someone put the same description for both lol.)

READ THIS FIRST —> :fire:

@hmwillett linked to this as the potential new recommended way to add custom fields to grids. While this post does have most of the information, it may not be the best example of what to do, especially if you don’t know what you are doing.

I will make a new thread soon with the basics in a safe way.

<— READ THIS FIRST :fire:

Ok, here is how I did it.

For the first part, here is what I did. I don’t think you actually need to do this part, since you said
your layer was working fine, but this is how it can be done.

Also, since the Kinetic Web Interface is still very much in flux, this may change at some
point and break, or need to be altered.

Remember, with great power, comes great responsibility…

When you click on Job Entry, the web interface calls the MetaFXSvc with a GET request like this:

https://yourserverurl/api/v1/Ice.LIB.MetaFXSvc/GetApp?request={"id":"Erp.UI.JobEntry","properties":{"deviceType":"Desktop","layers":[],"mode":null,"applicationType":"view","additionalContext":{"doValidation":false,"menuId":"UDJEC"},"checkDuplicateIds":false,"debug":false}}

That request is a json object like this:

request:

{
  "id": "Erp.UI.JobEntry",
  "properties": {
    "deviceType": "Desktop",
    "layers": [],
    "mode": null,
    "applicationType": "view",
    "additionalContext": {
      "doValidation": false,
      "menuId": "UDJEC"
    },
    "checkDuplicateIds": false,
    "debug": false
  }
}

The MetaFXSvc is a business object. Which means it has methods that can be manipulated.

You can find the MetaFX Service under Method Directives “ICE” → “Simple Service” → “MetaFX

You will add a Post-Processing Directive on that.

Here is my code that added the fields to the JSON, that the web client shell loads.

//using Newtonsoft.Json;
//using Newtonsoft.Json.Linq;

if(request.id == "Erp.UI.JobEntry")
{
    dynamic result2 = (dynamic)result;
    
    JArray columns = result2.Layout.components[0].model.gridModel.columns;
    
    columns.Add(new JObject(new JProperty("field", "ShortChar02")));
    columns.Add(new JObject(new JProperty("field", "Number01")));
    columns.Add(new JObject(new JProperty("field", "PrntRLQty_c")));
    
    JObject palletLabelQty = new JObject();
      palletLabelQty.Add( new JProperty("field", "PrntPLQty_c") );
      palletLabelQty.Add( new JProperty("title", "Pallet Lable Qty") );
    
    columns.Add(palletLabelQty);
}

And that will add your fields to the Landing Page. No customization required.

OK, on to the second part.

The app calls GetList from the Erp.BO.JobEntry Business Object.

The dataset does not include any UD fields by default. Here is what I did:

I added a Post-Processing Directive to “ERP” → “Business Object” → “JobEntry

and here is the code:

//Probably a more efficient way to do this? But anyway...
var newFieldsDictionary = (from table1 in result.JobHeadList.AsEnumerable()
                 join table2 in Db.JobHead.AsEnumerable()
                 on table1.JobNum equals table2.JobNum
                 select new 
                 {
                    JobNum = table1.JobNum,
                    NewFields = new
                    {
                        ShortChar02 = table2.ShortChar02,
                        Number01    = table2.Number01,
                        PrntPLQty_c = table2.PrntPLQty_c,
                        PrntRLQty_c = table2.PrntRLQty_c
                    }
                 }).ToDictionary(getKey => getKey.JobNum, getValue => getValue.NewFields);

//Add these columns/fields to the DataSet
foreach(var row in result.JobHeadList)
{
    row["ShortChar02"] = newFieldsDictionary[row.JobNum].ShortChar02;
    row["Number01"]    = newFieldsDictionary[row.JobNum].Number01;
    row["PrntRLQty_c"] = newFieldsDictionary[row.JobNum].PrntRLQty_c;
    row["PrntPLQty_c"] = newFieldsDictionary[row.JobNum].PrntPLQty_c;
}

And that was it.

I will post a sample in the next post so people can see some of the json.

3 Likes

Snippets of “Ice.LIB.MetaFXSvc/GetApp” → “Erp.UI.JobEntry” Json

BeforeMod:

{
  "Layout": {
    "namespace": "App.JobEntry.JobEntryForm",
    "name": "JobEntryForm",
    "guid": "2dcd1674-5e34-4d98-b493-c75747027376",
    "caption": "Jobs",
    "viewType": "Apps",
    "height": 734,
    "width": 1440,
    "primaryDataView": "",
    "navigation": {
      "pageCaption": "",
      "tabId": "",
      "enable": false,
      "parentPage": "",
      "epBinding": "LandingPage.JobNum",
      "epBindingLevel": null,
      "epBindingPeerOrder": null,
      "topRowTemplate": null,
      "dataSetId": "JobEntry"
    },
    "components": [
      {
        "id": "pcgLandingPage",
        "sourceTypeId": "metafx-panel-card-grid",
        "model": {
          "guid": "1f3b5db1-7042-4bc0-a8ba-f123ba16e2aa",
          "gridModel": {
            "id": "grdLandingPage",
            "epBinding": "LandingPage",
            "providerModel": {
              "svc": "Erp.BO.JobEntrySvc",
              "svcPath": "GetList",
              "restParams": {
                "whereClause": "Plant = '?{Constant.CurrentPlant}' and JobFirm = true BY CreateDate desc, JobNum desc"
              }
            },
            "columns": [
              {
                "field": "JobNum",
                "isLink": true
              },
              {
                "field": "PartNum"
              },
              {
                "field": "PartDescription",
                "title": "Part Description"
              },
              {
                "field": "ProdQty"
              },
              {
                "field": "StartDate"
              },
              {
                "field": "DueDate"
              },
              {
                "field": "RevisionNum"
              },
              {
                "field": "JobStatus"
              },
              {
                "field": "JobType",
                "erpEditor": "combo",
                "erpEditorModel": {
                  "textField": "display",
                  "valueField": "value",
                  "tableName": "JobHead",
                  "filters": [],
                  "fieldName": "JobType",
                  "required": false,
                  "isEpiReadOnly": false,
                  "appendList": false,
                  "dropDownStyle": "DropDownList",
                  "isDelimited": true
                }
              },
              {
                "field": "EquipID"
              }
            ]
          },

After Mod:

{
  "Layout": {
    "namespace": "App.JobEntry.JobEntryForm",
    "name": "JobEntryForm",
    "guid": "2dcd1674-5e34-4d98-b493-c75747027376",
    "caption": "Jobs",
    "viewType": "Apps",
    "height": 734,
    "width": 1440,
    "primaryDataView": "",
    "navigation": {
      "pageCaption": "",
      "tabId": "",
      "enable": false,
      "parentPage": "",
      "epBinding": "LandingPage.JobNum",
      "epBindingLevel": null,
      "epBindingPeerOrder": null,
      "topRowTemplate": null,
      "dataSetId": "JobEntry"
    },
    "components": [
      {
        "id": "pcgLandingPage",
        "sourceTypeId": "metafx-panel-card-grid",
        "model": {
          "guid": "1f3b5db1-7042-4bc0-a8ba-f123ba16e2aa",
          "gridModel": {
            "id": "grdLandingPage",
            "epBinding": "LandingPage",
            "providerModel": {
              "svc": "Erp.BO.JobEntrySvc",
              "svcPath": "GetList",
              "restParams": {
                "whereClause": "Plant = '?{Constant.CurrentPlant}' and JobFirm = true BY CreateDate desc, JobNum desc"
              }
            },
            "columns": [
              {
                "field": "JobNum",
                "isLink": true
              },
              {
                "field": "PartNum"
              },
              {
                "field": "PartDescription",
                "title": "Part Description"
              },
              {
                "field": "ProdQty"
              },
              {
                "field": "StartDate"
              },
              {
                "field": "DueDate"
              },
              {
                "field": "RevisionNum"
              },
              {
                "field": "JobStatus"
              },
              {
                "field": "JobType",
                "erpEditor": "combo",
                "erpEditorModel": {
                  "textField": "display",
                  "valueField": "value",
                  "tableName": "JobHead",
                  "filters": [],
                  "fieldName": "JobType",
                  "required": false,
                  "isEpiReadOnly": false,
                  "appendList": false,
                  "dropDownStyle": "DropDownList",
                  "isDelimited": true
                }
              },
              {
                "field": "EquipID"
              },
              {
                "field": "ShortChar02"
              },
              {
                "field": "Number01"
              },
              {
                "field": "PrntRLQty_c"
              },
              {
                "field": "PrntPLQty_c",
                "title": "Pallet Lable Qty"
              }
            ]
          },

Snippet of GetList DataSet from JobEntry

Before Mod:

{
  "JobHeadList": [
    {
      "ColumnNames": 0,
      "Company": "",
      "JobClosed": false,
      "ClosedDate": null,
      "JobComplete": false,
      "JobCompletionDate": null,
      "JobEngineered": true,
      "JobReleased": true,
      "JobHeld": false,
      "JobNum": "71854",
      "PartNum": "004100-0250-004",
      "RevisionNum": "A",
      "DrawNum": "",
      "PartDescription": "102in 2 1/2 MIL MS-320/1190/320 400#/RL NT (PRT W/ARROW)",
      "ProdQty": 3000.00000000,
      "IUM": "LB",
      "StartDate": "2022-10-26T00:00:00",
      "StartHour": 0.00,
      "DueDate": "2022-10-26T00:00:00",
      "DueHour": 4.29,
      "ReqDueDate": "2022-10-22T00:00:00",
      "JobCode": "",
      "QuoteNum": 0,
      "QuoteLine": 0,
      "ProdCode": "Foam",
      "ExpenseCode": "",
      "InCopyList": false,
      "WIName": "",
      "WIStartDate": "2022-10-26T00:00:00",
      "WIStartHour": 0.00,
      "Candidate": false,
      "SchedCode": "NORMAL",
      "SchedLocked": false,
      "ProjectID": "",
      "WIPCleared": false,
      "JobFirm": true,
      "PersonList": "",
      "PersonID": "",
      "ProdTeamID": "",
      "QtyCompleted": 0.00000000,
      "Plant": "MfgSys",
      "DatePurged": null,
      "TravelerReadyToPrint": false,
      "StatusReadyToPrint": false,
      "CallNum": 0,
      "CallLine": 0,
      "JobType": "MFG",
      "PhaseID": "",
      "AnalysisCode": "",
      "HDCaseNum": 0,
      "EquipID": "",
      "PlanNum": 0,
      "IssueTopicID1": "",
      "ExternalMES": false,
      "SysRowID": "3f13b96f-a3ea-43a9-85a7-989f31132126",
      "SOExists": false,
      "PartNumPartDescription": "102in 2 1/2 MIL MS-320/1190/320 400#/RL NT (PRT W/ARROW)",
      "PartNumTrackDimension": false,
      "PartNumTrackLots": true,
      "PartNumTrackSerialNum": false,
      "EquipOEM": "",
      "EquipModel": "",
      "EquipTypeID": "",
      "EquipLocID": "",
      "PMJob": false,
      "EquipDescription": "",
      "JobTypeName": "Manufacturing",
      "SmartString": "",
      "SmartStringProcessed": false,
      "AttributeSetID": 0,
      "AttrClassID": "",
      "AttrDescription": "",
      "ShortDescription": "",
      "JobStatus": "Released",
      "RowMod": "",
      "SpecifiedProperties": "////////////AQ==",
      "UserDefinedColumns": {}
    },

After Mod:

{
  "JobHeadList": [
    {
      "ColumnNames": 0,
      "Company": "",
      "JobClosed": false,
      "ClosedDate": null,
      "JobComplete": false,
      "JobCompletionDate": null,
      "JobEngineered": true,
      "JobReleased": true,
      "JobHeld": false,
      "JobNum": "71854",
      "PartNum": "004100-0250-004",
      "RevisionNum": "A",
      "DrawNum": "",
      "PartDescription": "102in 2 1/2 MIL MS-320/1190/320 400#/RL NT (PRT W/ARROW)",
      "ProdQty": 3000.00000000,
      "IUM": "LB",
      "StartDate": "2022-10-26T00:00:00",
      "StartHour": 0.00,
      "DueDate": "2022-10-26T00:00:00",
      "DueHour": 4.29,
      "ReqDueDate": "2022-10-22T00:00:00",
      "JobCode": "",
      "QuoteNum": 0,
      "QuoteLine": 0,
      "ProdCode": "Foam",
      "ExpenseCode": "",
      "InCopyList": false,
      "WIName": "",
      "WIStartDate": "2022-10-26T00:00:00",
      "WIStartHour": 0.00,
      "Candidate": false,
      "SchedCode": "NORMAL",
      "SchedLocked": false,
      "ProjectID": "",
      "WIPCleared": false,
      "JobFirm": true,
      "PersonList": "",
      "PersonID": "",
      "ProdTeamID": "",
      "QtyCompleted": 0.00000000,
      "Plant": "MfgSys",
      "DatePurged": null,
      "TravelerReadyToPrint": false,
      "StatusReadyToPrint": false,
      "CallNum": 0,
      "CallLine": 0,
      "JobType": "MFG",
      "PhaseID": "",
      "AnalysisCode": "",
      "HDCaseNum": 0,
      "EquipID": "",
      "PlanNum": 0,
      "IssueTopicID1": "",
      "ExternalMES": false,
      "SysRowID": "3f13b96f-a3ea-43a9-85a7-989f31132126",
      "SOExists": false,
      "PartNumPartDescription": "102in 2 1/2 MIL MS-320/1190/320 400#/RL NT (PRT W/ARROW)",
      "PartNumTrackDimension": false,
      "PartNumTrackLots": true,
      "PartNumTrackSerialNum": false,
      "EquipOEM": "",
      "EquipModel": "",
      "EquipTypeID": "",
      "EquipLocID": "",
      "PMJob": false,
      "EquipDescription": "",
      "JobTypeName": "Manufacturing",
      "SmartString": "",
      "SmartStringProcessed": false,
      "AttributeSetID": 0,
      "AttrClassID": "",
      "AttrDescription": "",
      "ShortDescription": "",
      "JobStatus": "Released",
      "RowMod": "",
      "SpecifiedProperties": "////////////AQ==",
      "UserDefinedColumns": {
        "Number01": 18.00,
        "PrntPLQty_c": 5,
        "PrntRLQty_c": 3,
        "ShortChar02": "Stand"
      }
    },
1 Like

@Justin_Grant you alive? :beers:

Cheers man. I really appreciate your detailed responses sir. Sorry for the late response. I’ve been swamped. I am going to try this shortly.

1 Like

Hey Kevin,

I really appreciate your help! For the first part, you’re right. It’s not technically needed, and it may not be the best way to add the columns for a couple of reasons, but I’m a sucker for code solutions so I love it. A couple caveats with it include the following. It would seem that the columns actually write into the database and I see some quirks popping up when I use it. for example, columns are duplicating especially while engaging with a customization layer and publishing. Am I the only one seeing this? Also, it’s probably better to add the columns in Application studio directly so that if someone later want to remove, change, or add, they aren’t going crazy trying to figure out why they keep showing up after removing them.

That being said, it can be hella confusing finding where to add them manually.

I simplified the code slightly and added a check to make sure the column doesn’t exist already before adding it in order to avoid duplication.

if(request.id == "Erp.UI.JobEntry")
{
  JArray columns = ((dynamic)result).Layout.components[0].model.gridModel.columns;
  
  var udfound = columns.Where(x=>x["field"].ToString()=="FieldID_c").FirstOrDefault(); 
  
  if(udfound==null) {
    columns.Add(new JObject{
      new JProperty("field", "FieldID_c")
      //,new JProperty("title", "FieldName") //optional
    });
  }
      
}

I think your linq join is probably the most efficient approach as far as execution is concerned. That should run once and then you have access to all the necessary data in memory for updating each row. I think most people would query individual jobs on each iteration of each row in the JobHeadList loop. I don’t think it would typically be very expensive, but it depends on how many columns and how many jobs, etc.

1 Like

Can you expand/explain that more, I’m very confused.

This as well. Did you have any duplicate items added ? Was your customization from before completely unloaded? I have seen some caching behavior before.

Also, I have not looked, but Application studio probably pulls the data a bit differently, so if you
were going to use this approach to modify the base, it might have to be modified to do the change
in more than one place.

I really didn’t look into it that far.

It is fascinating to poke at though.

In the first step, when modifying the MetaFX BO, there are a few things that seem to be happening and it’s hard to tell exactly what’s causing what, and it’s not just caching. I will preface the following with saying that I do have both the browser and the smart client open simultaneously which could be a factor in this. It’s also possible that the customization layer is somehow intercepting changes to the view on the fly and saving them to the data when I publish an update to the customization. I’m not convinced that’s the case but it’s still possible.

The most consistent and problematic thing I’m seeing is that whatever you write into that object (even post-processing) appears to keep in the personalization settings. If it’s a field that wasn’t already available to the view, it adds it “somewhere” and stays whether I clear the cache or not. I didn’t monkey with it further to see if fields already there duplicate or not in that list of settings or to figure out what table or view is keeping those.

I also see that the fields show up in Application studio in the customization layer grid settings as columns that have been added and can be configured in the grid. I can change the type, and label, etc. and that will change the programmatically added options permanently. Unfortunately, deleting the columns from here do not delete them from the personalization settings either. It’s behaving a bit like they become rogue entries in a table that is meant to be keyed to another but isn’t so they are stuck.

I also noticed briefly in one test case where fields were actually pulling into the grid multiple times. I will say that clearing the cache did seem to fix this problem though.

All of that being said, I really think the first step isn’t needed at all if you are referencing existing UD Fields tied to JOBHead. For whatever reason, Epicor already has those as optional personalization settings without actually populating the values in the JobHeadList. Your second BPM on get list should be all that’s needed to fix the problem.

Would you mind sharing what you found in the breakout thread I made?

(Good or bad, right or wrong, let’s discuss :slight_smile: )

https://www.epiusers.help/t/manipulating-the-kinetic-web-ui-from-the-backend-for-fun-and-profit/100267

In case anyone goes further down this road, I don’t want the discussion to get lost.

sure. I can do that.

1 Like