Uplifting Code from C# to Function

We have a UD form that allows production to match and unmatch serial numbers. It works great in the customization world. We are uplifting the form to be used in the browser. Which means the C# code needs to be converted to a function. The C# code to match was uplifted with no issues!!!

The Unmatch code is the problem. Almost the last line of code to be uplifted. :frowning:

Showing you all of the code we are seeing, maybe another set of eyes can get this over the finish line.

Original C# code that uses an Adapter.

private void msUnMatchSerialNumber(string strParentSerialNum, string strParentPartNum, string strRevNum, string strJob, int intAssembly, string strChildSerialNum, string strChildPart)
	{
		bool blnReturn = false;
		string strOut1 = string.Empty;
		string strOut2 = string.Empty;
		string ipType = "mtl";
		System.Int32 intMtlSeq = 0;

		SerialMatchingAdapter SMAdapter = new SerialMatchingAdapter(oTrans);
		SMAdapter.BOConnect();
			
		blnReturn = SMAdapter.ChangeSerialNum(strParentSerialNum, strParentPartNum, strRevNum, strJob, intAssembly, out strOut1, out strOut2);
		blnReturn = false;

		SMAdapter.SerialMatchingData.SerialMatchHdr[0].RowMod = "U";
		SMAdapter.SerialMatchingData.SerialMatchAsmbl[0].RowMod = "U";
		
		blnReturn = SMAdapter.ValidateAssembly( strJob, intAssembly, out strOut1);
		blnReturn = false;
		int i = SMAdapter.SerialMatchingData.SerialMatchMtl.Count;
		int intDelete = 0;
        
		SMAdapter.SerialMatchingData.SerialMatchHdr[0].RowMod = "U";
		SMAdapter.SerialMatchingData.SerialMatchAsmbl[0].RowMod = "U";

		
		//Loop throught the data set to get the serialnumber to be unmatched		
		foreach (Erp.BO.SerialMatchingDataSet.SerialMatchMtlRow row in SMAdapter.SerialMatchingData.SerialMatchMtl)
		{
			if (row.MtlPartNum == strChildPart && row.MtlSerialNo == strChildSerialNum )
			{
				intMtlSeq = row.SeqNum;
				row.RowMod = "U";
				row.MtlSerialNo = "";
				break;
			}
		}		
		
		blnReturn = SMAdapter.UpdateSMMaterial("J", strChildSerialNum, intMtlSeq);
		
		SMAdapter.SerialMatchingData.SerialMatchHdr[0].RowMod = "U";
		SMAdapter.SerialMatchingData.SerialMatchMtl[1].RowMod = "U";

		blnReturn = SMAdapter.SetFullyMatched(-1);
		
		// Dispose of item
		SMAdapter.Dispose();
	}

Uplifting to a function so we can convert the UD Form to the Browser.
The issue line is:
SMContract.UpdateSMMaterial(ipType, ChildSerialNumber, intMtlSeq, ref smts);

The method call will not update the database. The message box shows that the SerialMatchMtl record looks to be set correctly. Anything else would stop the call from saving? No error message as well. Very odd.

string ChildSerialNumber;
string ChildPart;
string ParentSerialNumber = inputSystem.Tables[0].Rows[0]["SerialNo_SerialNumber"].ToString();
string ParentPart = ParentSerialNumber.Substring(0, 7);
string JobNumber = inputSystem.Tables[0].Rows[0]["SerialNo_JobNum"].ToString();
string Out1;
string Out2;
string ipType = "J";
int JobAsm = Convert.ToInt32(inputSystem.Tables[0].Rows[0]["JobAsmbl_AssemblySeq"].ToString());

bool blnReturn = false;
Erp.Tablesets.SerialMatchingTableset smts;


this.CallService<Erp.Contracts.SerialMatchingSvcContract>( SMContract =>
{

	foreach (DataRow grdRow in inputMatchedSNs.Tables[0].Rows)
	{ 

		this.PublishInfoMessage($"Values: {ParentSerialNumber}, {ParentPart}, {JobNumber}, {JobAsm}", Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual, "", "");

		  
		ChildSerialNumber = grdRow["SerialNo1_SerialNumber"].ToString();
		ChildPart = grdRow["Part_PartNum"].ToString();

		smts = SMContract.ChangeSerialNum(ParentSerialNumber, ParentPart, "", JobNumber, JobAsm, out Out1, out Out2);

		smts.SerialMatchHdr[0].RowMod = "U";
		smts.SerialMatchAsmbl[0].RowMod = "U";

		SMContract.ValidateAssembly(JobNumber, JobAsm, out Out1, ref smts);

		var smMtl = smts.SerialMatchMtl.Where(r1 => r1.MtlPartNum == ChildPart && r1.MtlSerialNo == ChildSerialNumber).FirstOrDefault();

		if (smMtl != null)
		{			
			int intMtlSeq = smMtl.SeqNum;
			smMtl.RowMod = "U";
			smMtl.MtlSerialNo = "";

			this.PublishInfoMessage($"Values: {smMtl.MtlPartNum}, {smMtl.MtlSerialNo}, {intMtlSeq}, {ChildSerialNumber}", Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual, "", "");          

			smts.SerialMatchHdr[0].RowMod = "U";
			smts.SerialMatchAsmbl[0].RowMod = "U";
			
			SMContract.UpdateSMMaterial(ipType, ChildSerialNumber, intMtlSeq, ref smts);
		}
		  
		smts.SerialMatchHdr[0].RowMod = "U";
		  
		foreach(var row in smts.SerialMatchMtl)
		{
			row.RowMod = "U";
		}

		SMContract.SetFullyMatched(-1, ref smts);


	}

});
2 Likes

I’ve had similar issues in the past with JobOpDtl when trying to update scheduling resources. I was able to get around that by making a copy of the row and placing it into the dataset before changing data and RowMod so Epicor knew what changed.

The important part is:

var newJOD = (Erp.Tablesets.JobOpDtlRow) jobDS.JobOpDtl.NewRow();
  BufferCopy.Copy(jobOpDtl, newJOD);
  jobDS.JobOpDtl.Add(newJOD);

And you should be able to do something similar for the serial matching data row.

// Change resource on job if resource doesn't match method's resource. Then release the job.
CallService<Erp.Contracts.JobEntrySvcContract>(je => {
  var job = jobNum;
  var jobDS = je.GetByID(job);
  
  var jobOpDtl = jobDS.JobOpDtl.FirstOrDefault(op => op.OprSeq == 10 && !op.ResourceGrpID.ToUpper().Contains("TOOL"));
  // Make a copy of the row we're updating so that Epicor will actually update the records!
  var newJOD = (Erp.Tablesets.JobOpDtlRow) jobDS.JobOpDtl.NewRow();
  BufferCopy.Copy(jobOpDtl, newJOD);
  jobDS.JobOpDtl.Add(newJOD);
  
  jobOpDtl.RowMod = IceRow.ROWSTATE_UPDATED;
  
  je.ChangeJobOpDtlResourceID(jobResources[job], ref jobDS);
  
  //jobDS.JobOpDtl.FirstOrDefault(op => op.OprSeq == 10 && !op.ResourceGrpID.ToUpper().Contains("TOOL")).RowMod = IceRow.ROWSTATE_UPDATED;
  je.Update(ref jobDS);
  
  // Now release the job.
  jobDS = je.GetByID(job);
  
  var jobHead = jobDS.JobHead.FirstOrDefault();
  var newJobHead = (Erp.Tablesets.JobHeadRow) jobDS.JobHead.NewRow();
  BufferCopy.Copy(jobHead, newJobHead);
  jobDS.JobHead.Add(newJobHead);
  
  jobHead.JobReleased = true;
  jobHead.RowMod = IceRow.ROWSTATE_UPDATED;
  
  je.ChangeJobHeadJobReleased(ref jobDS);
  je.Update(ref jobDS);
  log.WriteLine($"    Changed resource and released job {job}.");
});
11 Likes

That did the trick!!

Thanks a bunch.

added the following to the code after the if(smMtl != null)…

        var newSmMtl = (Erp.Tablesets.SerialMatchMtlRow) smts.SerialMatchMtl.NewRow();  
        BufferCopy.Copy(smMtl, newSmMtl);  
        smts.SerialMatchMtl.Add(newSmMtl);       
4 Likes

That’s what @josecgomez lovingly calls the “BeforeImage”.

It’ll get ya!

2 Likes

Its actually an OLD OLD Progress term

Open Edge Progress DBMS has something called a

Before Image
The Before Image file(s) stores information about every transaction which makes a change to the database. If the transaction is aborted / undone, this information will be read back from the BI file to restore original values of the database back into the database if necessary.

It also has an After Image

Think of these as Undo / Redo functions built into the DMBS. Until flushed any transaction can be undone / redone as needed. You can also query the before image record to see what changed between transactions. Epicor took advantage of this in the YE OLD times, it is how they do the Field Changed from X to Y (among other things)

When Epicor went to MSSQL they simply took that concept and adapted it. Every single BO call the adapter makes contains two records, the ORIGINAL UNCHANGED RECORD and the MODIFIED RECORD (usually with a RowMod flag). This way they can easily see what changed between calls.

85% of the time it doesn’t matter… that last 15% will bite you in the :peach:. Technically any time we call the BO’s (server side) we should pass in a BeforeImage with every call. But is is tedious and so most of us only do it when forced to.

In the new REST Calls in Kinetic UX if you look at the Network Monitor you will see that every record that is sent from the UX has those 2 records one with a RowMod and one without.

8 Likes

Then you add BPM with condition like field value changed from one to another and suddenly it matters again :slight_smile:

5 Likes

that 15% always catches me by surprise haha, the other day I deleted a header record cause I didn’t include it on an update :upside_down_face:

2 Likes

Yes, I’ll admit that I haven’t started adding it back to all my stuff (even new :sheep:), but I ask myself “Do I have any BPMs on this I need to check” lol.

Burned me more than I’d like to admit.

4 Likes

In the modern world the idea of relying on something sent from client is so wrong.

3 Likes

Agreed! it hurts my brain daily. Take it on as your 20% project :yum::yum: does Epicor do 20%

4 Likes

I am far behind my current 20% project :sweat_smile:
Probably we just need to sign BI rows to verify the are not changed

3 Likes

ooooh…

Tell Oh My GIF by Robert E Blackmon

5 Likes

Hahaha