Kinetic Application Studio Best Practices: Am I doing this right?

I’m still a noob when it comes to Kinetic Application Studio. My request here is to review my approach and ask “Did I do this in a reasonably correct way”?

Requirement TLDR:
On the Job Tracker/Job Entry screen, populate a value that is retrieved elsewhere.

Details:
In our implementation, A job’s first operation on the “main” manufacturing line is Assembly 0, Operation 40. User wants to see the StartDate/time for Assembly 0/Op 40 on the header level page of the Job.

In pseudo-sql, we need to retrieve this:

Select
StartDate
from erp.JobOper jo
where JobNum = ‘xxxxxx’ and AssemblySeq = ‘0’ and OprSeq=40;

Approach I took
I created a custom view KCC_Calculated.Op40StartDate. to hold the value.

I created function (DisplayFunctions.GetOp40StartDate) to accept JobNum as the input parameter and return Op40StartDate. If the job hasn’t started yet, or if we can’t find it, we return an empty string for the Op40StartDate.

I created a text box control, set it to read only. I set Behavior “On Create” to call the function passing JobHead.JobNum. It returns a string “Op40StartDate”.

In the row-update event, I bind the field to Op40StartDate, and assign it the Value “{actionResult.Op40StartDate}”.

This is the text of the function.

I am open to all comments (with the possible exception of the formatting of the date/time, as it was reusing the legacy code, and that’s “the way it’s always been”.)

//Wrap everything in the service context
this.CallService<Erp.Contracts.JobEntrySvcContract>
  (svc =>
    {
      // set to true to see debug popup messages.
      var debug = false;
      
      // return default blank value 
      this.Op40StartDate = "";

      //This will create a structured JobEntryTableset object.
      var jets = svc.GetByID(this.JobNum);

      //Loop through JobOper
      foreach (var jo in jets.JobOper)
      {
        var oprSeq = jo.OprSeq;
        
        if ((int)jo.OprSeq == 40)
        {
          if (debug) this.PublishInfoMessage("StartDate =" + jo.StartDate + ", StartHour=" + jo.StartHour, Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual, "", "");
          
          // calculate new Op40StartDate
          if (jo.StartHour != null && jo.StartDate !=null)
          {
            decimal startHour = (decimal)jo.StartHour;
            decimal formattedTime = Math.Round(((startHour - Math.Truncate(startHour)) * 60 * 0.01m) + Math.Truncate(startHour), 2);
        
            var Text = Convert.ToDateTime(jo.StartDate).ToString("MM/dd/yy") + " " + formattedTime.ToString();
                        
            this.Op40StartDate = Text;
            
            if (debug) this.PublishInfoMessage("Op40StartDt =" + Text, Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual, "", "");            
          }

        } // if
      } // foreach JobOper record
    } // svc
  );

Hey Greg!

This may be easier than you might imagine. In Kinetic, many of the things displayed are held in DataViews. If you open Job Tracker, go into Developer Tools, then click back on the form and enable epDebug (I do, Ctl+Alt+8). Then in the Console, type:

epDebug.views

This will show all the DataViews and you will find JobOper there. All of the operations will already be available to you. No need to call back to the database to get the JobOper records, including:

image

The next excercise is to extract that StartDate and set it to a DataView to display.

OnInit of your field will leave this unchanged if someone changes the job they are looking at without backing out of the Header screen. I would probably use an event trigger like “After DataView Changed” focusing on the JobHead dataview.

Aside from that, because you’re just lookup up a couple values, you don’t really need to pull the whole Tableset into memory, or really use the service at all. I would just add the JobOper to the Table References as read only, and query the DB

// set to true to see debug popup messages.
var debug = false;

// return default blank value 
this.Op40StartDate = "";

// Simple Query to the Database Table, already filtered for your Assembly and OprSeq.
// Stores less in memory, doesn't require loop.
var jo = Db.JobOper
                .Where( w => w.Company == Session.CompanyID
                          && w.JobNum  == this.JobNum 
                          && w.AssemblySeq == 0
                          && w.OprSeq == 40 )
                .Select( s => new {s.StartHour, s.StartDate} )
                .FirstOrDefault();

// If jo is null, Opr doesn't exist.
if ( jo == null )
{
    this.Op40StartDate = "Asm 0 Opr 40 Does Not Exist";
    return;
}

// Already filtered for that row, no need to loop
if (debug) this.PublishInfoMessage("StartDate =" + jo.StartDate + ", StartHour=" + jo.StartHour, Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual, "", "");

// calculate new Op40StartDate
if (jo.StartHour != null && jo.StartDate !=null)
{
    decimal startHour = (decimal)jo.StartHour;
    decimal formattedTime = Math.Round(((startHour - Math.Truncate(startHour)) * 60 * 0.01m) + Math.Truncate(startHour), 2);

    var Text = Convert.ToDateTime(jo.StartDate).ToString("MM/dd/yy") + " " + formattedTime.ToString();
                            
    this.Op40StartDate = Text;
    
    if (debug) this.PublishInfoMessage("Op40StartDt =" + Text, Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual, "", "");            
}

Your reply was posted while I was working on mine, and yours is better lol. Are all the rows in the JobOper dataview held in there at once? Meaning you can look up a value from any row even if a different oper was selected?

That has been my experience.

Interesting. I was doing something similar with bringing PartPlant records into the front screen of Part Entry, but if someone looked at a different plant record then went back to the main screen, the value changed. I must have missed something in my binding.

Switching sites may be different than looking at Operations in a Job. I see all operations while looking at the header screen. Other tables may behave differently. :person_shrugging:

**EDIT:

Yes. JobOpDtl is refreshed when a different JobOper is selected. But all JobOper records are there from the get go.

First, thank you for your replies.

I apologize for the long delay in responding to your answers.

I had knee surgery, and was off work for several weeks, and then have been dealing with catching up, etc.

I am going to start a new post, because I think I have taken the wrong approach here from the start.

Specifically, I wrote a function that updates the database (not the screen buffer), for the JobOper and JobOpDtl tables. Then we are left with a mismatch of data input buffer, and database values. Specifically, we have “dirty” values in the buffer where the JobHead UD field has been updated in the data buffer, but not the database. Other fields may also have been updated, but not yet written, if the user hasn’t pressed Save. Then when the UD field is changed, our magic behind-the-scenes db update, does what I want, but it isn’t reflected in the input screens. They still show the old values. I tried adding an event-next “Refresh” event, so the JobOper and JobOpDtl values would properly display, but that caused all the modified values in JobHead to be “reset” from the database, since the db didn’t have the “new values”.

I think we should be using a more “Kinetic Application Studio”-native method, rather than a subroutine update-the-db-behind-the scenes approach.

New post coming shortly.

In the meantime, I will accept the one answer that I think was pointing me in the right general direction.

@KVE makes a good point above. Kinetic is trying to be efficient on how much data to download. It doesn’t bring down the entire object, but enough to show the first level - at least that’s how it appears. So, I see all JobOper records, but we only have a few. It brings down no JobOprDtls until I select a JobOper. Test your case to make sure you don’t have to refresh list as Kevin did above. If it works, great, but program defensively!