Improve existing BPM that auto-generates Part Number with unique prefix/sequence

Thanks to lots of posts here on how to auto-generate numbers I have a working BPM (Post-Processing on Part.GetNewPart) to populate the Part Number field with a prefix/sequence based on a user selection in a BPM Data Form when “New Part” is clicked**.
My question is: Do I need to have all these Condition/Set Field widgets or can I consolidate to one of each with some correctly written C# in Set Field? (Details below)

PartType

(Changing our part numbering logic is a whole different issue -I’m working on that but right now just trying to eliminate an Access DB that is used solely for creating new part numbers… don’t ask)

It works fine but was wondering if there was a way to reduce all my Condition/Set Field widgets to just one Condition (‘user clicks OK on the form’) and then test for the possibilities in one Set Field expression.

What I have now:


image

Where I specify the sequence to use in each of the Set Field widgets uniquely per choice made in the form using “new Ice.Lib.NextValue(Db).GetNextSequence(“ST_PT_NumSeq”).ToString(“PT00000”)” changing the Sequence name accordingly.

What I think I ‘should’ have:
AutoPN2

and use something like this in the Set Field expression:

if(callContextBpmData.Character01 = "PT")  
{
  new Ice.Lib.NextValue(Db).GetNextSequence("ST_PT_NumSeq").ToString("PT00000");
}
if(callContextBpmData.Character01 = "RM")  
{
  new Ice.Lib.NextValue(Db).GetNextSequence("ST_RM_NumSeq").ToString("RM00000");
}
if(callContextBpmData.Character01 = "CM")  
{
  new Ice.Lib.NextValue(Db).GetNextSequence("ST_CM_NumSeq").ToString("CM00000");
}

However, I do not know how to get past the “Expression should contain only one statement” issue as I really have no idea how to write C#.

Any help is greatly appreciated!

**Note - this only works in the classic form… in the Kinetic interface it presents the choice over and over again and never gets to the New Part form. If there is an easy fix to this please let me know!

You need to have == where you have =.
Each one of those if statements is true the way you have it written. ADditionally, you may want to use else ifs for each subsequent possibility.

Side note: I’m very interested in this topic and have been wanting to do something like this. How do you lookup the next sequencing number?

Immediately edit. I thought you were in a custom code widget block. Ok for that, you will have to use the ? : syntax. so it would look like this:

callContextBpmData.Character01 == "PT" ? new Ice.Lib.NextValue(Db).GetNextSequence("ST_PT_NumSeq").ToString("PT00000") : ""

(if statement) ? (if true do this) : (else do this)

2 Likes

Awesome @dr_dan - thank you! My BPM now looks like this:
image

with this as the expression in Set Field:

callContextBpmData.Character01 == "PT" ? 
new Ice.Lib.NextValue(Db).GetNextSequence("ST_PT_NumSeq").ToString("PT00000") : 
callContextBpmData.Character01 == "RM" ? new Ice.Lib.NextValue(Db).GetNextSequence("ST_RM_NumSeq").ToString("RM00000") :
callContextBpmData.Character01 == "CM" ? new Ice.Lib.NextValue(Db).GetNextSequence("ST_CM_NumSeq").ToString("CM00000") :
callContextBpmData.Character01 == "MFG" ? new Ice.Lib.NextValue(Db).GetNextSequence("ST_MFG_NumSeq").ToString("MFG00000") :
callContextBpmData.Character01 == "CAT" ? "Enter Catalog Number" : ""

The last line is because we have a whole different nomenclature for our ‘Catalog’ items and those will just get typed in directly.

As far as looking up the next number, each of those sequences (“ST_PT_NumSeq”, “ST_RM_NumSeq”, etc.) get created in the Ice.SysSequence table the first time they are called via GetNextSequence.

This is in my test DB:
image

Is that what you were looking for with your question?

Side Note:
I tried to use SetSequenceCurrentValue from this post but couldn’t get the syntax correct:

We are on-premises and I have access to SQL so I committed the mortal sin of manually changing the “CurrentValue” field for each of those Sequences in SQL to match our last used part number, so that the next new one would be incremented by 1.
image
This is literally the one time I felt fairly comfortable changing something in the DB directly since it did not exist until I created it, and nothing else SHOULD care about that one specific value.

1 Like

Very cool. Thanks for getting back about it. I think we’re going to do something similar.

Now I just need to figure out how to make it work in the Kinetic interface… :rage:

You shoudl replace this ugly if statement with some logic that makes it easier such as this so that there are no ifs… just build out the two strings, and do the one next value:

string seqCode = "ST_"+callContextBpmData.Character01+"NumSeq";
string formatIt = callContextBpmData.Character01 + "00000"
new Ice.Lib.NextValue(Db).GetNextSequence(seqCode).ToString(formatIt)
2 Likes

Thanks @timshuwy! I think I still need one ‘if’ statement because if the user selection is “CAT” we are using custom, non-sequenced numbering. I was thinking I should test for that first, then use your code as the ‘else’, so I tried the following but am back to the “Expression should only contain one statement” message.

callContextBpmData.Character01 == "CAT" ? "Enter Catalog Number" :
string seqCode = "ST_"+callContextBpmData.Character01+"NumSeq";
string formatIt = callContextBpmData.Character01 + "00000"
new Ice.Lib.NextValue(Db).GetNextSequence(seqCode).ToString(formatIt) : ""

Is there an easy fix to that?

Putting the IF statement “first” would be the way I would do that.

1 Like

Complete ignorance here, but I thought that’s what I was doing by putting the ‘CAT’ ? statement at the beginning. I thought I could then use your code as the ‘else’ (after the colon) for the other cases. That’s where I am running into the ‘Expression should contain only one statement’ message. I am guessing it is some very simple syntax error on my part, but not sure what needs to change.

image

Ahh… you cannot have multiple statements in one of those in-line if statements. You will need to use a regular if statement:

if (x=y) {
   //do something
} else {
   //do something else;
   //do another thing;
}
1 Like

I’m sure you are now regretting commenting on this, but I do appreciate your help as I fumble through this :sweat_smile:
I am clearly missing some fundamental understanding of how this should work. Based on your last note I tried this but am still getting the same “Expression should contain only one statement” error:

if (callContextBpmData.Character01 == "CAT")
  {
  "Enter Catalog Number"
  } else {
string seqCode = "ST_"+callContextBpmData.Character01+"NumSeq";
string formatIt = callContextBpmData.Character01 + "00000"
new Ice.Lib.NextValue(Db).GetNextSequence(seqCode).ToString(formatIt)
  }

But based on @dr_dan’s comment earlier in this thread:

I thought you were in a custom code widget block. Ok for that, you will have to use the ? : syntax…

it almost sounds like there is a difference in behavior between a custom code widget and the ‘Set field’ widget I am using. Does that have anything to do with my struggles or am I still just missing something simple in the syntax? Sorry to go down a rabbit-hole on this, but now I am very curious how to resolve it.

Think of it this way. Those set widgets are only designed for one line of code. So you can’t use a ; there. If you have to, then you need to do that in a separate widget before that widget and use the variable you set previously in the single line command.

I dont see where you are expecting “Enter Call Number” to go, even in your old code.

Thanks @dr_dan - and that covers my fundamental misunderstanding!

@timshuwy What I am doing with “Enter Catalog Number” is populating the Part Number field with that phrase when ‘CAT’ is selected on the form, because those part numbers are constructed manually, not with a sequence. Eventually this will all change, but right now I needed to get rid of a 15 year-old Access DB that was being used solely to generate part number for the other categories.

Thanks for your help!

A long time ago we did it in a Customization and simply reading the database. (This should be a BPM, but will be converted within time). It also queries 100 last records, so we can find gaps within and fill them.

This gives you an idea of example code what you could do. We use a Special UD Code Dropdown.

/// <summary>
/// Generate Next Part Number
/// </summary>
/// <param name="typeID">Passes in the Category like PG_Groups or PG_ClassIDHere</param>
/// <param name="prefix">Category or Group</param>
/// <returns>PartNum</returns>
private string GetNextPartNum(string typeID, string prefix)
{
    if (string.IsNullOrEmpty(prefix)) {
        return string.Empty;
    }

    // If typeID is PG_Groups shorten the prefix to 4 Characters
    if (typeID == "PG_Groups")
    {
        prefix = prefix.Length >= 4 ? prefix.Substring(0, 4) : prefix.Substring(0, prefix.Length);
    }

    int nextSeq = 0;
    string sDashPrefix = this.PNGPartPrefix;

    using (BOReaderImpl bor = WCFServiceSupport.CreateImpl<BOReaderImpl>((Ice.Core.Session)oTrans.Session, Epicor.ServiceModel.Channels.ImplBase<Ice.Contracts.BOReaderSvcContract>.UriPath))
    {
        // Search TOP 15 Rows it should be enough
        string whereClause = string.Format("Company = '{0}' and PartNum LIKE '{1}-{2}[0-9][0-9][0-9][0-9][0-9]%' BY PartNum DESC", ((Ice.Core.Session)oTrans.Session).CompanyID, prefix, sDashPrefix);
        DataSet ds = bor.GetListWithPaging("Erp:BO:Part", whereClause, 100, string.Empty);

        if (ds != null && ds.Tables[0].Rows.Count > 0)
        {
            var list = ds.Tables[0].AsEnumerable().Select(s => Convert.ToInt32(Regex.Match(s.Field<string>("PartNum"), @"(?<=\D)\d+").ToString() ));
            int missingSequence = Enumerable.Range(list.Min(), list.Count()).Except(list).FirstOrDefault();

            // Use Missing Sequence found or Max
            if (missingSequence >= 1)
            {
                return string.Format("{0}-{1}{2}", prefix, sDashPrefix, missingSequence.ToString().PadLeft(5, '0'));
            }

            nextSeq = list.Max();
        }
    }

    nextSeq += 1;
    return string.Format("{0}-{1}{2}", prefix, sDashPrefix, nextSeq.ToString().PadLeft(5, '0'));
}

AHH… Now I see what you are doing… you are in a SETTER widget using C# in the setter… I missed what kind of widget you were in.
You cannot do what you want to do in this type of a setter widget, BUT you can do it with a regular C# widget.

but FIRST you should create a VARIABLE in the BPM that can be used to pass data. Then you can use that variable in your c# code widget.

In my example below, I previously created the variable MyReturnValue and then used it

if (callContextBpmData.Character01 == "CAT") {
  **MyReturnValue** =  "Enter Catalog Number";
} else {
  string seqCode = "ST_"+callContextBpmData.Character01+"NumSeq";
  string formatIt = callContextBpmData.Character01 + "00000";
  **MyReturnValue** = new Ice.Lib.NextValue(Db).GetNextSequence(seqCode).ToString(formatIt);
}

Thank you everyone! I am now creating the variable, using @timshuwy’s code, then setting the PartNum field in the Setter.
Note: I had to set the Custom Code widget to execute ‘Synchronously’ as Asynchronously would throw a ‘Failed to enable constraints’ error (screenshot below**)

Also, just to clarify for anyone else, all three iterations of the BPM I posted do work fine, but to Tim’s point I could now just add a new value to my form and not have to go in and update the Directive code and/or widgets.

So I definitely achieved my goals of learning something and improving my existing BPM. Here is final layout:
image

**Async error:
image