I’m working on a few BPMs written by another consultant in the past. One of them is for our Data Collection (MES) and is for ensuring the quantity produced on a job operation does not exceed the quantity completed on any of the previous operations. In short, you can’t machine 10 shafts (Op Seq 20) if the saw operator reported he only cut 5Op Seq 10).
The BPM executes a custom code and I just can’t figure out what all of it is saying. I can see where it is saying “You cannot record more parts than are finished on the previous operation” but I don’t understand whats triggering the last part that says “Couldn’t locate the item”
Could someone with more knowledge look at it an help me out?
TIA
foreach (var labor in ds.LaborDtl)
{
// if not end activity or the labor quantity = 0 then move along, nothing to see here
if (!labor.EndActivity || labor.LaborQty == 0) continue;
// get the operations
var operations = (from row in Db.JobOper
where row.Company == labor.Company && row.JobNum == labor.JobNum
orderby row.AssemblySeq, row.OprSeq
select new {row.AssemblySeq, row.OprSeq, row.QtyCompleted}).ToList();
// create subset so that we can get an index to work with
var operationSearch = (from row in operations
orderby row.AssemblySeq, row.OprSeq
select new {row.AssemblySeq, row.OprSeq}).ToList();
// find the current operation in the list
var currentIndex = operationSearch.IndexOf (new {labor.AssemblySeq, labor.OprSeq});
if (currentIndex > 0)
{
var previousQty = operations [currentIndex - 1].QtyCompleted;
var currentQty = operations[currentIndex].QtyCompleted + labor.LaborQty;
if (currentQty > previousQty)
{
throw new Ice.BLException ("You cannot record more parts than are finished on the previous operation.");
}
}
else {
throw new Ice.BLException ("Couldn't locate the item.");
}
}
var currentIndex = operationSearch.IndexOf (new {labor.AssemblySeq, labor.OprSeq});
if (currentIndex > 0)
{
//clearer now?
}
else {
throw new Ice.BLException ("Couldn't locate the item.");
}
The provided code appears to be in C# and it’s performing some database operations, probably within an Entity Framework context, which is a popular ORM (Object-Relational Mapper) for .NET. Here’s a line-by-line breakdown of what the code is doing:
foreach (var labor in ds.LaborDtl): This line starts a loop over all labor items in ds.LaborDtl. ds.LaborDtl is likely a collection of labor detail records from a dataset (presumably a database).
if (!labor.EndActivity || labor.LaborQty == 0) continue;: This line is checking if the EndActivity property of the current labor item is false or if LaborQty (quantity) is zero. If either of these conditions is true, the loop will skip the current iteration and move on to the next labor item.
The next block of code creates a list called operations which is filled with a subset of data from the Db.JobOper table (or dataset). The subset of data selected includes rows where the Company and JobNum match the corresponding properties of the current labor item. The subset is then sorted by AssemblySeq and OprSeq, and only the AssemblySeq, OprSeq, and QtyCompleted fields are selected.
The next block of code creates another list called operationSearch. This is another subset of the operations list, sorted by AssemblySeq and OprSeq, and only includes the AssemblySeq and OprSeq fields. This appears to be done to create a simplified list to search against in the next step.
var currentIndex = operationSearch.IndexOf (new {labor.AssemblySeq, labor.OprSeq});: This line finds the index of the current labor item in the operationSearch list.
if (currentIndex > 0): If the currentIndex is greater than zero (meaning the current labor item was found in the operationSearch list and is not the first item), it performs the following operations.
It retrieves the QtyCompleted of the previous operation and the current operation (with the added labor.LaborQty).
If the currentQty is greater than the previousQty, it throws an exception with the message “You cannot record more parts than are finished on the previous operation.”
else { throw new Ice.BLException ("Couldn't locate the item."); }: If the currentIndex is not greater than zero (meaning the labor item was not found in the operationSearch list), it throws an exception with the message “Couldn’t locate the item.”
In summary, this code is looping through a set of labor details, checking a condition, and for each labor detail that meets the condition, it’s comparing the quantity completed in the current operation against the previous one. If the quantity completed in the current operation exceeds the previous, it raises an exception. It also checks whether the current operation exists in the list, and if it doesn’t, an exception is thrown.
Thanks for helping me out on this everyone. Where we are having problems seems to be the second exception being thrown. Operators are trying to end activity on the jobs with a correct quantity and the exception of “Cannot locate object” keeps getting thrown.
We want the warning of not entering more quantity than previous operations but I don’t get the “Cannot locate item” Is it saying the operation (or previous one) is missing? Is there an easy way to turn the exception into a notification?
I really need to learn more C#,
Again many thanks.
looking at your version, in Kinetic you will be able to add some debug code in to dump the values of the labor,Assembly and labor.Oprseq to help work out the current index is -1 (not found) or not.
You can use in your code and flip the debug flag on the directive and the information will show in the event log on your app server
#if DEBUG
Ice.Diagnostics.Log.WriteEntry($" currentIndex is: {currentIndex}");
#endif
This is just one of many approaches
The “Couldn’t locate the item” message is less than explanatory as to why you arrived there.
Wondering why the code above has a $ in it then do a quick google for string interpolation c# or ask ChatGPT, Bard…Et al
Here’s a link as well
Getting your head around c# is going to help you a lot on your journey.
The Db query in the loop is questionable. That is usually the wrong way to go about it. I typically do all the query stuff at the top and work with the (hopefully well filtered) dataset beneath, including any loops.
“That guy” is always needed. Here’s most of the back story.
We hired consultants to help us write a series of BPMs to help control how shop floor labor is entered. This is required by the CFO for costs to flow correctly. We backflush everything (I know, I know) and have employees whose computer skills range from being able to identify one if it fell on them to figuring out how to cheat the system.
We have a BPMs to not allow quantities more than the job, disallow ending operations if another employee is also in the same one, disallow completing operations out of order, checking if enough material is available before starting (notice) and when ending (exception), and this one, not allow a quantity that is more than the previous operation.
All but two BPMs are working and we aren’t working with that consulting group on this. I’ve been asked to figure it out on my own.
I’ve spent 30 years running a factory and have learned Epicor from sweat equity. I am now the ERP admin for our small company. I can write BAQs, BPMs, do customizations, dabble in SQL, and maintain databases all within limits. I was a combat engineer, not a software engineer. I know C# will need to added to my skillset very soon.
With all that said, I need to not allow a shop employee ending activity through MES to enter a quantity that is greater than the quantity completed on the previous operation. If there is no previous op then he can proceed. He can add 0 (zero) or any quantity up to and equal to the quantity of the previous operation but he cannot enter more.
I have hoped that this might be an easy fix and I could salvage this particular BPM with this code. I have no problems writing a better code (or BPM) to accomplish the task required.
I hope this helps explain how I got to this bridge in my journey.
All of these things are achievable without custom code. I don’t think you even need BPMs (If your operators can be trusted to not flat out ignore a waring)
Shop warnings can display messages to MES operators for all of these scenarios and more, based on criteria you set up:
There is also a feature called ‘Production Yield Recalculation’ that will update the expected production yield on a job based on the quantity entered by an operator in MES.
Thank you. Yes we do have those warnings turned on and they should stop at completing an op if the prior hasn’t been completed. We’re just trying to “add guardrails” to keep the shop on track.
of course, there are always improvements that can be made to programs… I would enhance the initial query to include the IF condition so that you are not looping through a bunch of records that are going to get “continued”.
@rturrentine I put this code in my system and ran some start and end activities. The else is only hit on the first Op, but if you do not get a quantity on the first Op you are done.
Your code is fine if you just remove the else, but below is what I used to follow it thru.
/* check qty on end */
Ice.Diagnostics.Log.WriteEntry("start end act check qty ");
foreach (var labor in ds.LaborDtl)
{
// if not end activity or the labor quantity = 0 then move along, nothing to see here
if (!labor.EndActivity || labor.LaborQty == 0) continue;
Ice.Diagnostics.Log.WriteEntry($"Found Op {labor.OprSeq} Code {labor.OpCode} ");
// get the operations
var operations = (from row in Db.JobOper
where row.Company == labor.Company && row.JobNum == labor.JobNum
orderby row.AssemblySeq, row.OprSeq
select new {row.AssemblySeq, row.OprSeq, row.QtyCompleted}).ToList();
// create subset so that we can get an index to work with
var operationSearch = (from row in operations
orderby row.AssemblySeq, row.OprSeq
select new {row.AssemblySeq, row.OprSeq}).ToList();
// find the current operation in the list
var currentIndex = operationSearch.IndexOf (new {labor.AssemblySeq, labor.OprSeq});
Ice.Diagnostics.Log.WriteEntry($"Index {currentIndex} Op {labor.OprSeq} Code {labor.OpCode}");
if (currentIndex > 0)
{
var previousQty = operations [currentIndex - 1].QtyCompleted;
Ice.Diagnostics.Log.WriteEntry($"prev Op qty {previousQty}");
var currentQty = operations[currentIndex].QtyCompleted + labor.LaborQty;
Ice.Diagnostics.Log.WriteEntry($"prev qty this Op {operations[currentIndex].QtyCompleted} new Qty {labor.LaborQty}");
if (currentQty > previousQty)
{
throw new Ice.BLException ($"You cannot record more parts than are finished on the previous operation. {previousQty:0}");
}
}
/*
else {
InfoMessage.Publish ("Couldn't locate the item.");
}
*/
}