Mark all Quote lines as Engineered

Hey gang, before you write vague references to things like “write a function”, “use a BPM”, or “fuggle the liftvarhaven” I am new to the world of BPM and Functions so I will need direction on where to even start. The only thing I know is what is possible and I know how to access Method Directive and Function Maintenance, but I wouldn’t even know where to start after that. So please either point me at some good training or take me step by step if you can. I’ve looked at a few posts similar to what I want and I got lost very quickly as the people in the threads were talking at a higher level.
I do have minimal Application Studio experience, I have a fair SQL background so BAQs are not too bad for me, I do also have programming background (admittedly on a deprecated language, but I can more or less handle C#). OK enough about me and my situation, now on to the challenge at hand.
We will rely very heavily on Epicor CPQ for our product line items. We are pretty close to having 20 CPQs completed, I anticipate that 95%+ of our line items will be driven by CPQ. Our sales team will be handling Quotes with upwards of 50 line items at times. Asking them to check the “Engineered” button on each line before sending a quote will be a real time suck. Epicor quotes cannot be printed until all lines are marked as Engineered. CPQ line items come into the Quote as Part on the fly, therefor the Engineered flag is not set. Only one of our CPQ products will actually require Engineering to review prior to quote submission. We would like to allow our sales team to send quotes without waiting and without the time suck.
I theorize we could write an Epicor Function to check the “Engineered” flag on each line item at the click of a button on the Quote screen. The reason I say Function and not BPM is that we may want to leave the lines in the un-Engineered state to allow CPQ editing until right before quote submission.
I know likely comments might steer in a different direction however let me make a few direct questions in case I am on the right path:

  1. Is an Epicor Function tied to a button on the Quote Entry page the right direction?
  2. When setting up the Function I am assuming (making an arse out of myself) I would be pretty much using a code block, but if there is somewhere else I need to start please point me in a direction. I don’t mind if you point me at documentation or training, I have been looking. So far I feel overwhelmed knowing where to start.
  3. Is there any reference materials on the code i can use in these code blocks and how to reference the particular Quote Detail lines in a loop to check if they are checked or not and make the appropriate change?

PS we are working with a consultant on other pieces already and could have them work on this, but I feel I will need to learn sooner or later and I think now is the time.
Also thanks in advance this community has already been a great help.

1 Like

You’re definitely on the right track here. BPMs are executed based on when the Epicor Business Object methods are called (Method Directives) or when specific tables are updated (Data Directives), so if you’re wanting more user control, a function is probably the way to go.

I tend to use custom code functions, but I think you could probably do this one with widgets. I would take a look at the Epicor documentation for Functions (Open up Functions Maintenance, click Help > Application Help).

For something like this, the “Update Table by Query” widget would probably do the job. In your function, you would pass in the quote number from the entry form that your user is working in as a Request Parameter (set in Function > Signature tab).

You would then use the “Invoke BO Method” to call the “Quote.GetByID” method, which will give you the Quote Tableset for the quote you’re working with. The Tableset is a collection temporary tables (QuoteHed, QuoteDtl, QuoteAsm, etc) that the Business Object uses for updating data.

Next, you can use the Update Table by Query widget to update your quote lines to engineered. If you’ve used the BAQ editor, it’s very similar. You can add the QuoteDtl table, then bind the values to the query results or specified expressions. That last part is the key, because it will let you set the QuoteDtl.Engineered field to true. You’ll also need to set the QuoteDtl.RowMod field to "U", which tells the Business Object that row needs to be Updated (U = Updated, A = Added, D = Deleted).

image

Then the last thing to do would be to invoke the BO Method “Quote.Update”, which will then save / update all the tables with populated “RowMod” fields.

If you’d prefer to use custom C# code, that can be done too. Personally, I find using code preferable to using widgets, but that’s just me. The code below essentially does the exact same thing as the widgets above, using the same BO Methods and everything:

// First, use CallService to use the Business Object

this.CallService<Erp.Contracts.QuoteSvcContract> (svc =>
{
    // Next, retrieve the Quote Tableset using the GetByID method
    Erp.Tableset.QuoteTableset QuoteTS = svc.GetByID( MyQuoteNum );

    // Now you have the Tableset, you can loop through the Quote Lines
    foreach ( var quoteLine in QuoteTS.QuoteDtl )
    {
        // Now, set your Engineered field to true and your RowMod field to "U"
        quoteLine.Engineer = true;
        quoteLine.RowMod = "U";

        //Update the Tableset to save the changes to the row
        svc.Update( ref QuoteTS );
    }
});

The Epicor Help is pretty useful for understanding the basic flow and how to use the function designer. For understanding how to code a custom code function, I find it more useful to look at the functions people have posted on this forum, especially if you can find one that does something similar.

I hope this helps!

3 Likes

Correct me if I’m wrong, but If you do the widgets, update by query will just change the field in the dataset, but I don’t believe it’s actually going to call the update method. And I don’t know if you can update more than one row at a time. (Each BO is a little different and some allow it, but most don’t). So if you’re going to use widgets, you’re going to have a set up a loop and another widget for update, right?

Edit: you did say something about update… I just looked at the picture. (oops). But the question about the loop still stands.

2 Likes

I should probably bold the part just below those screencaps about adding the Invoke BO Method “Update”. It does get lost since I drop the code in there just below it.

That might be the case. In the code I always put the Update inside the loop, I had thought Update Table by Query allowed multiple row updates. I don’t use widgets very often, so there’s a good chance I’m wrong on that. If that’s the case, there’s definitely some extra widget work that needs to be done.

1 Like

Me too. And the widget will update the fields. But in the UI, whenever you change lines, the system does a save, so functionally, you can never have more than one row change at a time. Because of that, most BOs only take one change at a time. It’s one of the reasons I don’t like widgets. There are many reasons to need to loop, and it’s really challenging to do with the widgets.

2 Likes

OK gang, thank you for the input. I skipped some of the first big post and see some comments that this approach may not work because I would be trying to update multiple records. Before I go digging further into the steps in that very nice first response, do I need to modify the approach before I get started?

It’s not that it won’t work, but you’ll probably have adjust the widget approach and make a loop. Some people have done it if you search around, so it’s possible, but since @kve already gave you the C#, it’s probably easier just to go with that.

2 Likes

I am finally getting around to working on this. @kve response is phenomenal. Now I have a dumb question. How do I run the function? Or is there a safe way to test it?

For testing, I usually go with the “Schedule Epicor Function” module (just search the menu). It ask for your function library and function name, and then it will open a slide-out for you to enter any Request Parameters that you added to the function (Most likely QuoteNum).

1 Like

OK I need to debug. The lines did not get marked as ReadyToQuote as I had hoped. For debugging purposes is there a way to through out useful feedback into the Task Log or somewhere else? BTW No error, but I did put some conditions on the Function. I am curious if the condition is messed up and causing it to not mark the lines, but I would like to know if that is the case or not.
Basically if a Quote Line is marked “Engineering Required” I want to throw a message to the user when they run this. I will mark the Engineer field from CPQ for quote lines that will need Engineering to review before the quote can be sent to the client. We hope that most lines eventually will not need this flag, and I want the sales people to not need to manually click the “ReadyToQuote” flag on each line item in order to send a quote to a customer. Many of our quotes will have well over 20 line items. While 20 checkboxes doesn’t sound like much it can be tedious.

There sure is!

To use the function below, you’ll need to add the Tables SysTask and SysTaskLog, and SysMonitorTasks Service.

For testing I highly recommend this tool from @josecgomez

2 Likes

I’m making progress, so far I am using the tools from the pallet as my first attempt the the code failed. Then again my first bunch of attempts with the pallet tools did as well, LOL. I think I was missing references to tables and parameters, so I could probably go back to the C# method. Anyhow my next challenge is that my Library/Function are not available in the button method. Not sure what I need to do to make the Library/method available for the button.

*** Nevermind found it ***

1 Like

OK even better, the button is on the page and working. However for the end user it might not look like it is working as you would need to refresh the page. Can you trigger a refresh from a function?

You could add a rest call to the button event that calls the function. In the function widget properties under Behavior, select OnSuccess and then call the event that loads the data you want to see refreshed.

Thank you so very much, but I’m currently struggling with how to do this. Either I’m not sure what event to call or how to call it.

Are you familiar with using the browser dev tools to watch what events are happening? F12, Ctrl+Alt+V etc? If not there’s a great writeup from Hannah somewhere that I can find and link.

Using this on the opportunity/quote screen we can see that when you click the refresh button in the main tool bar, the event that fires is called OnClick_toolRefresh. So in the event you have for your button to call the function, you can add an event-next widget and point it to OnClick_toolRefresh. You could try to find a more detailed event, but it sounds like a general refresh would do what you want.

I feel like I should know this. I do know how to open the dev tools and look at the console but I am getting lost. I will go looking for Hannah’s post. Thanks again. BTW that “OnClick_toolRefresh” worked perfectly. I had it set to “PerformUpdate” that I saw in the “ViewChanging” event. I am clueless, fumbling in the dark. Thank you so very much.

Here’s the post from Hannah