Job firming based on trace works in Rest and BLTester, but throws "update not allowed" in BPM

We want to firm jobs based on criteria, after MRP runs every day.

I have put a data directive on Ice.Systask using the Task Description and the Process Set name to trigger this.

Using the trace log, if I unfirm a job manually I get

image

Which makes sense, although it’s not clear why it always calls Update() twice.

If I trace a DMT unfirm it’s much simpler:

image

This makes even more sense.

Using DMT, there’s no problem firming these jobs. I only have five columns:

image

I can firm jobs by REST or BLTester, but not from my BPM.

If I use REST in the Swagger UI I can firm jobs. It doesn’t work if I try to work in a different site to my last activity, but I gues if I was using Rest I’d specify that in headers.
image

If I use the BLTester, with only GetByID and Update, everything just works.

image

I’ve managed to make it fail in BLTester two ways:

  • If I don’t set the plant using Tools > Change Plant before invoking GetByID

Or

  • If I make the change to the dataset AFTER pulling up the Update() method (but before invoking it).

That’s of interest because it gives me the same error as I’m getting in my BPM: “Update not allowed. Job not firm”. So if I select the Update() method and then change the dataset, I get this error, but if I change the flag right after GetByID() and THEN select Update() and immediately click Invoke, everything works.

The other thing I notice in BLTester is the JobFirm flag has 3 states. One click and it’s ticked, one more click and the tick turns grey, and one more click and the tick mark is cleared.

My BPM code is here:

//SF_JobFirmer
Ice.Diagnostics.Log.WriteEntry("BPM Triggered");

if  ( (from t in ttSysTask where t.TaskDescription.Equals("Process MRP") /* && t.ProcessID.Equals("MRP Reg")*/ select t).Any()   ) //MRP Just Ran (TODO: add called by process set MRP Reg)
  {
  using (CallContext.Current.TemporarySessionCreator.SetCompanyID("RDCAN").Create() )
    {
    using (var txScope = IceContext.CreateDefaultTransactionScope() )
      {
        
      DateTime futureDate = DateTime.Today.AddDays(30);
      var job = (from jh in Db.JobHead
                  join jp in Db.JobProd on jh.JobNum equals jp.JobNum
                  join rl in Db.OrderRel on jp.OrderNum equals rl.OrderNum
                  join oh in Db.OrderHed on rl.OrderNum equals oh.OrderNum
                  
                  where jp.OrderNum > 0 &&
                        jp.WIPQty > 0 &&
                        jp.OrderNum == rl.OrderNum &&
                        jp.OrderLine == rl.OrderLine &&
                        jp.OrderRelNum == rl.OrderRelNum &&
                        jh.JobEngineered == true &&
                        oh.OpenOrder == true &&
                        oh.ReadyToFulfill == true &&
                        rl.OpenRelease == true &&
                        rl.FirmRelease == true &&
                        rl.ReadyToFulfill == true &&
                        oh.RequestDate <= futureDate
                        
                  select jh);
      foreach (var j in job)
        {
        if ( (j.JobFirm == false) )
          {
          
        var bo = Ice.Assemblies.ServiceRenderer.GetService<Erp.Contracts.JobEntrySvcContract>(Db);
       
        var ds = bo.GetByID(j.JobNum);
        
        ds.JobHead[0].JobFirm = true;
        ds.JobHead[0].RowMod = "U"; //tried "U", "M" and without this, no difference
        bo.Update(ref ds);

          }
        }
      txScope.Complete();
      }
    }
  }

``cs

Anybody see anything? I haven't been able to find too much on this service contract.

anyone?

:cry:

I would try using the job status business object instead of job entry. It’s much faster since you don’t need to do a full GetByID on the job. Do a trace on firming a job in job status maintenance to see what needs to be called. Server side and client side bo calls don’t always work the same and it’s really frustrating when they don’t.

1 Like

ah, thanks for a fresh direction to try. I’ll trace that and see.

A start…

var js = Ice.Assemblies.ServiceRenderer.GetService<Erp.Contracts.JobStatusSvcContract>(Db);
string JobNum = "UNF00000000025";
string NewJobNum = "New Job"; // Need to get new Job Num
var ds = js.GetByID(JobNum);
ds.JobHead[0].ToFirm = true;
ds.JobHead[0].RowMod = "U";
js.ChangeJobHeadFirm(true,NewJobNum, ref ds);
ds.JobHead[0].RowMod = "U";
ds.JobHead[0].JobFirm = true;
ds.JobHead[0].ExtUpdated = true;
js.MassUpdate(ref ds);
2 Likes

Try using the BeforeImage

2 Likes

whoa, my rabbit hole has branch tunnels. OK, will dig into this and see what happens. Thanks a million.

OK, awesome - seems like this works. I have a couple of things I don’t know but will keep working on.

(1) I implement the BeforeImage using

          var origRow = ds.JobHead.NewRow();
          BufferCopy.Copy(ds.JobHead[0], origRow);
          ds.JobHead.Add(origRow);

but I really don’t know why. I read the articles @josecgomez linked to, and a few others, and it seems this has to do with backwards-compatibility to Progress? Or is that completely off the mark? What’s actually happening here - are we making a backup copy of the row inside the dataset, and then committing both to the DB? Or are we just acknowledging the change as a quality control?

(2) I couldn’t get JobEntryImpl to work in any way; however JobStatus definitely solved it! Based on my debugging messages to server logs, the ChangeJobHeadFirm() method threw an error until I removed the string NewJobNum... piece. As far as I can tell, using the original job number results in the job being firmed, and using a job number beginning with UN (our unfirm prefix) results in a new job with FIR (our firm profix). Am I missing something?

Many thanks @josecgomez and @Carson !

For anyone this might help, my final, working, code is here:

//SF_JobFirmer

if  ( (from t in ttSysTask where t.TaskDescription.Equals("Process MRP") && t.ProcessID.Equals("MRP Reg") select t).Any()   ) //MRP Just Ran, called by process set "MRP Reg"
  {
  using (CallContext.Current.TemporarySessionCreator.SetCompanyID("RDCAN").Create() )
    {
    using (var txScope = IceContext.CreateDefaultTransactionScope() )
      {
      Ice.Diagnostics.Log.WriteEntry(Session.UserID.ToString());
        
      DateTime futureDate = DateTime.Today.AddDays(30);
      var job = (from jh in Db.JobHead
                  join jp in Db.JobProd on jh.JobNum equals jp.JobNum
                  join rl in Db.OrderRel on jp.OrderNum equals rl.OrderNum
                  join oh in Db.OrderHed on rl.OrderNum equals oh.OrderNum
                  
                  where jp.OrderNum > 0 &&
                        jp.WIPQty > 0 &&
                        jp.OrderNum == rl.OrderNum &&
                        jp.OrderLine == rl.OrderLine &&
                        jp.OrderRelNum == rl.OrderRelNum &&
                        jh.JobEngineered == true &&
                        oh.OpenOrder == true &&
                        oh.ReadyToFulfill == true &&
                        rl.OpenRelease == true &&
                        rl.FirmRelease == true &&
                        rl.ReadyToFulfill == true &&
                        oh.RequestDate <= futureDate
                        
                  select jh);
      foreach (var j in job)
        {
        if ( (j.JobFirm == false) )
          {
          var bo = Ice.Assemblies.ServiceRenderer.GetService<JobStatusSvcContract>(Db);
          var ds = bo.GetByID(j.JobNum);
          
          //create Before image 
          var origRow = ds.JobHead.NewRow();
          BufferCopy.Copy(ds.JobHead[0], origRow);
          ds.JobHead.Add(origRow);
          
          //update record
          string jobNum = j.JobNum;
          ds.JobHead[0].ToFirm = true;
          ds.JobHead[0].RowMod = "U";
          bo.ChangeJobHeadFirm(true,jobNum, ref ds);
          ds.JobHead[0].RowMod = "U";
          ds.JobHead[0].JobFirm = true;
          ds.JobHead[0].ExtUpdated = true;
          bo.MassUpdate(ref ds);
          }
        }
      txScope.Complete();
      }
    }
  }
3 Likes

So the BeforeImage was something that existed in Progress (yes) but it has always been baked intothe Epicor Architecture.

On BPMS for example you can have conditions like Field changes from X to Y in order to check this Epicor uses the BeforeImage.

The BeforeImage is just a pristine(unmodified) copy of the current row as it was retrieved from the database. This makes it really Easy for Epicor BL to see what changed.

Epicor also uses this internally in their BL to check various conditions. For example if you change fields in a Released Job (other than those fields allowed etc)

It allows the logic to quickly check for changes without having to go back to the database and query again the (old) record

are we making a backup copy of the row inside the dataset, and then committing both to the DB?

All we are doing is providing the Epicor BL and BPM engine with a copy of the original record so that it can compare our changes and provide validation.

96% of the time if you don’t send in the BI epicor figures it out anyways, but there are some corners of the logic that still rely on this (like you just found)

1 Like

@SteveFossey - thank you for posting the working solution. Having this as a reference helped me out with a somewhat related issue I was having.

1 Like