Creating an Approval Process using a UD Table

I setup my UD20 table to use for managing several different approval processes. Lets take adding a negative amount value to a OHOrderMsc row for this example. Here is my question, which of these approaches makes more sense, or is there an even better way to do this?

  1. On Method Directive “Erp.BO.SalesOrder.ChangeMiscAmount / Pre-Processing / [Name]” when The “ds.OHOrderMsc.DocDspMiscAmt” field of “the changed row” “is less than” the “0.00…” expression is true, I run C# like this. I set the bool existingUD20Row as a bpm variable.
// Add using Ice.Assemblies; to Usings
// Add Ice.Contracts.BO.UD20 to References
// Add Erp.Contracts.BO.SalesOrder to References
// Create Variables
string company = "";
int orderNum = 0;
int orderLine = 0;
int seqNum = 0;
decimal discAmt = 0.00m;
int custNum = 0;
decimal orderTotal = 0.00m;
existingUD20Row = false;

// Get Temp OHOrderMsc Row
var tempOHOrderMsc = ds.OHOrderMsc.FirstOrDefault();
if(( tempOHOrderMsc != null ))
{
  // If Temp Row Exists, Set Variables Values
  company = tempOHOrderMsc.Company;
  orderNum = tempOHOrderMsc.OrderNum;
  orderLine = tempOHOrderMsc.OrderLine;
  seqNum = tempOHOrderMsc.SeqNum;
  discAmt = tempOHOrderMsc.DocDspMiscAmt;
}

// If variables contain values continue
if(( company != "" && orderNum != 0 ))
{
    // Get OrderHed for custNum and orderAmt
    var orderHed = Db.OrderHed.Where( oh => oh.Company == company && oh.OrderNum == orderNum ).FirstOrDefault();
    if(( orderHed != null ))
    {
        custNum = orderHed.CustNum;
        orderTotal = orderHed.DocOrderAmt;
    }
    // Call UD20 Service
    using (var ud20svc = ServiceRenderer.GetService<Ice.Contracts.UD20SvcContract>(this.Db))
    {
        // Create Variables for UD20 Row
        string recordType = "Sales Order";
        string requestType = "Discount";
        // Check if a User has already requested a Discount for this Sales Order
        var UD20_Row = (from row in Db.UD20 where row.Key1 == orderNum.ToString() && row.Key2 == recordType && row.Character03 == requestType && (string.Compare(row.Company, Session.CompanyID, true) == 0) select row).FirstOrDefault();
        if((UD20_Row == null))
        {
            // Request Doesn't Exist, Add Request to UD20
            var ud20Ts = new UD20Tableset();
            ud20svc.GetaNewUD20(ref ud20Ts);
            var ud20New = ud20Ts.UD20.FirstOrDefault();
            ud20New.Key1 = orderNum.ToString();
            ud20New.Key2 = recordType;
            ud20New.Key3 = custNum.ToString();
            ud20New.Key4 = discAmt.ToString();
            ud20New.Key5 = Session.UserID.ToString();
            ud20New.Character01 = "";
            ud20New.Character02 = "";
            ud20New.Character03 = requestType;
            ud20New.Character04 = orderTotal.ToString();
            ud20New.Number01 = orderNum;
            ud20New.Number02 = orderTotal;
            ud20New.Number03 = orderLine;
            ud20New.Number04 = seqNum;
            ud20svc.Update(ref ud20Ts);
        }
        else
        {
            // Notify User Request already exists - using Condition and a Notify User widget
            existingUD20Row = true;
        }
    }
}

Then I would add a user notification widget and an email widget. Then set the Misc Amount to Zero.

  1. On Method Directive “Erp.BO.SalesOrder.MasterUpdate / Pre-Processing / [Name]” when There is at least on “updated” row in the “ds.OHOrderMsc” table is true, The “ds.OHOrderMsc.DocMiscAmt” field of “the updated row” “is not equal to” the “0.00…” expression is true, and The “ds.OHOrderMsc.DocMiscAmt” field has been changed from “any” to “another” “And” The “ds.OHOrderMsc.DocMiscAmt” field of “the updated row” “is less than” the “0.00…” expression is true, I Enable Post Directive.
    On Method Directive “Erp.BO.SalesOrder.MasterUpdate / Post-Processing / [Name]” when This directive has been enabled from the “[Name]” directive is true, I run C# like this.
    Same as above C#, plus:
// Get OHOrderMsc Db Row and set MiscAmt && DocMiscAmt value to 0.00
var orderMsc = Db.OrderMsc.Where( om => om.Company == company && om.OrderNum == orderNum && om.OrderLine == orderLine && om.SeqNum == seqNum ).FirstOrDefault();
if(( orderMsc != null ))
{
    orderMsc.MiscAmt = 0.00m;
    orderMsc.DocMiscAmt = 0.00m;
    Db.Validate(orderMsc);
}

// Notify User Approval Required

I’d add a Notification Widget and an Email Widget, etc.

I would really appreciate anyone’s feedback and suggestions on which route makes more sense or if there is another approach that would be even better.

Then I plan on setting up UD20 approval method directives, and maybe I’ll come back and document that.

Wow… one of the new features that will be displayed at Insights will be the ability to do approvals via Email with our new product that has a mini-preview here: Insights Sneak Peek... to learn more, come to Insights 2022

One of the other samples (that I don’t have a video of) does an approval cycle for Purchase Orders. It sends a really nice email with active buttons. The email looks like this (and yes, those buttons work)… want to know more? Come to Insights 2022!

1 Like

That is really cool sounding @timshuwy! Thank you for sharing that, I can’t wait to start testing that new functionality and that email looks awesome!

Only issue is I need to get this functionality working for a number of field changes across multiple tables, and I can’t wait for the feature to release to do it. However, when the functionality is released, I will definitely be testing it out and seeing how I can implement it into our processes. I 100% agree with built in functionality over customization wherever possible.

Note that this will not be a “free” module. It is an add-on tool but with the tool will be multiple free “Samples” that you can immediately implement.

You mentioned there is, ‘an approval cycle for Purchase Orders.’ Will the add-on tool allow an approval cycle for other Business Objects?

Sure… you can use this same template to put any approvals you want. It might need some UD Fields to control if something needs approved or is approved, but once you have a trigger condition, you could do something like “approve price variance”, “approve Engineering Change”, etc. I did create a sample to "notify of an open TASK on a QUOTE with a deep link to the quote. Took a few minutes to build.

Something you want to tell us Tim? :thinking:

1 Like

@Mark_Wonsil I can confirm that deep linking is already available in 2022.1.X :slight_smile: (and even sharing to facebook shudder)

1 Like

@brandtley have you given Case Entry some thought? Natively Case Entry can be linked to (almost) anywhere in the system and it has built in strong workflows which can be setup and used for approvals of any kind.
We are currently using case management for Order Entry approvals from our Sales Force. CSR Enters an Order, a Case is Created with the appropriate workflow attached, task is assigned to Sales Rep, he/she approves goes to Sales Manager etc.

There’s a lot of piping already available there that could be useful. Mind you we dont have anyone going into Case Entry itself, we are using Case as the backend for our workflow.

1 Like

So I should be getting three votes back! :rofl: 38 other votes will be going back as well!

So far Method One has been working okay after a bit of testing. So here is the BPM I setup for the UD20 approval process.

Step 1.
On Method Directive “Ice.BO.UD20.Update / Pre-Processing / [Name]”, I setup a condition to confirm the updated UD20 row is the correct one to update the OHOrderMsc row requested. Then I have it Enable Post Directive.

Step 2.
On Method Directive “Ice.BO.UD20.Update / Post-Processing / [Name]”, a condition makes sure it was called from the pre-processing directive, if true I run the following C#.

// Add using Ice.Assemblies; to Usings
// Add Ice.Contracts.BO.UD20 to References
// Add Erp.Contracts.BO.SalesOrder to References
using(var txscope = IceDataContext.CreateDefaultTransactionScope())
{
    // Get changed UD20 row and set variables
    var UD20Row = ds.UD20.FirstOrDefault();
    string companyNum = (string)UD20Row.Company;
    int orderNum = Convert.ToInt32(UD20Row.Number01);
    int orderLine = Convert.ToInt32(UD20Row.Number03);
    int seqNum = Convert.ToInt32(UD20Row.Number04);
    string miscAmt = (string)UD20Row.Key4;
    decimal discAmt = Convert.ToDecimal(UD20Row.Key4);
    
    // Set variables to use when deleting the UD20 row later
    string key1 = (string)UD20Row.Key1;
    string key2 = (string)UD20Row.Key2;
    string key3 = (string)UD20Row.Key3;
    string key4 = (string)UD20Row.Key4;
    string key5 = (string)UD20Row.Key5;
    
    // Find the OHOrderMsc row that needs updating
    var orderMscRow = Db.OrderMsc.Where( omr => omr.Company == companyNum && omr.OrderNum == orderNum && omr.OrderLine == orderLine && omr.SeqNum == seqNum ).FirstOrDefault();
    if(( orderMscRow != null ))
    {
        orderMscRow.MiscAmt = discAmt;
        orderMscRow.DocMiscAmt = discAmt;
        Db.Validate(orderMscRow);
    }
    else // If it had been deleted since the request, you could add a new row
    {
        // I'm going to write something to add a row to the OHOrderMsc for the orderNum
    }
    
    // Find the OHOrderMsc row to make sure it got added or updated and the requested value exists
    var salesOrderMsc = Db.OrderMsc.Where( om => om.Company == companyNum && om.OrderNum == orderNum && om.OrderLine == orderLine && om.SeqNum == seqNum ).FirstOrDefault();
    if(( salesOrderMsc != null && salesOrderMsc.DocMiscAmt == discAmt ))
    {
        // Get the UD20 row and remove it
        var thisUD20Row = Db.UD20.Where( tr => tr.Key1 == key1 && tr.Key2 == key2 && tr.Key3 == key3 && tr.Key4 == key4 && tr.Key5 == key5 ).FirstOrDefault();
        if(( thisUD20Row != null ))
        {
            using(var UD20svc = ServiceRenderer.GetService<UD20SvcContract>(Db))
            {
                UD20svc.DeleteByID(key1,key2,key3,key4,key5);
            }
        }
    }
txscope.Complete();
}

Then I have a notification popup to let the user now it was successful.

This has worked on some basic tests, so I hope it will continue to. Again, I would truly appreciate feedback and/or suggestions. If nothing else, I hope somebody finds this helpful for their project.

@josecgomez thanks for the suggestion. I will play around with case management and see if I can find an easier method. I will update the thread if I find anything.

I’ve always wanted to play with this and build my own:

image

But not Facebook…

It does it based on your browser but here’s what you get on Edge

I can’t wait to tweet my Epicor Links.

Okay, so I spoke too soon. I am currently getting the following error. “OHOrderMsc record not available.” If anyone could offer some help, I would be very grateful. Thank You.

so yes, Idea ERP-I-516 is complete in 2022.1… when you the menu in any screen, there is now a “Share” menu option. If you choose the menu, you get a SHARE pop-up that allows you to press “Copy Link”… if you put that link into an email and send it, the person who receives it will be able to follow the link directly to the application and the record you linked to. They OF COURSE will need to log in if they are not already logged in. The deep link looks like this:

https://My.EpicorInstance.com/ERPCurrent/apps/erp/home/#/view/MRMT1010/Erp.UI.PartEntry?channelid=0&company=EPIC06&site=EVN&pageId=Details&KeyFields.PartNum=00C1

Notice how you can see part number “00C1” in the link? if you change the part number, you will get a different part. Imagine the possibilities…

1 Like

So this seems to be working flawlessly, but…

This is giving me errors. Specifically…

Would really appreciate anyone’s input. Thank You.

Never mind, I had another BPM running that was interfering. The concept works :ok_hand:

Update: I’ve added a Zapier process to update the UD20 Row via a reply to the Epicor email notification. If anyone is using this in the future, its a great way to make the approval process faster and more mobile.