PO Box 1128 Yorktown Heights, NY 10598
+1 914-407-2442
+1 914-407-2432

Insights 2016 Code Reference

Rob Bucek from D & S Manufacturing and Jose C Gomez did a joint presentation at Epicor® Insights 2016 exposing some advanced BPM and customization techniques that can be used to enhance your Epicor 10 ERP. Below you will find the code we demonstrated during our presentation. We have done our best to explain the concepts we are demonstrating but if you have any questions feel free to reach out. Please note that this code is freely available for your use as is, but we do not claim it does anything in particular nor are we responsible if it causes performance issues or other issues with your system. Always test your code heavily before deploying to production.

Also during our presentation we mentioned that it can be very useful to write / debug your BPM code in Visual Studio. Visual Studio is a free and can be downloaded from Microsoft. Epicor provides a sample template and guide on how to debug / write BPMs in Visual Studio and that is available at the Epicor Ice 3.0 Migration Site along with very nice tools to convert ABL to C#. However NEVER (almost never) use the converted code in this utility directly in your production system, the code that it produces is a nice start but should be optimized to work with E10 and it can lead to very serious performance issues if you just take whatever the tool gives you directly into your production environment.

D & S Sales Order BPM

This BPM is used to calculate the Request Date (Ship Date) in an order based on Delivery Days defined in the customer shipto

private void PostProcessing_D7BC5CFD9FF3EBB5E311CE4BE3BAD4B7_A001()
{
                /* Calculate ShipBy from Delivery Days (Customer ShipTo Entry) */
    int shipbymod = 0;
    DateTime? newShipDate = null;
    int delivdays = 0;
    decimal daysearly = 0;
    Erp.Tables.OrderHed OrderHed;
    Erp.Tables.ShipTo ShipTo;
    foreach (var ttorderdtl_iterator in (from ttorderdtl_Row in ttOrderDtl
                                         where string.Equals(ttorderdtl_Row.RowMod, IceRow.ROWSTATE_ADDED, StringComparison.OrdinalIgnoreCase) || string.Equals(ttorderdtl_Row.RowMod, IceRow.ROWSTATE_UPDATED, StringComparison.OrdinalIgnoreCase)
                                         select ttorderdtl_Row))
    {
        var ttorderdtlRow = ttorderdtl_iterator;
            foreach (var orderhed_iterator in (from orderhed_Row in Db.OrderHed.With(LockHint.NoLock)
                                               where string.Compare(orderhed_Row.Company, ttorderdtlRow.Company, true) == 0 && orderhed_Row.OrderNum == ttorderdtlRow.OrderNum
                                               select orderhed_Row))
            {
                OrderHed = orderhed_iterator;
                ShipTo = (from ShipTo_Row in Db.ShipTo.With(LockHint.NoLock)
                          where string.Compare(OrderHed.Company, ShipTo_Row.Company, true) == 0 && OrderHed.CustNum == ShipTo_Row.CustNum && string.Compare(OrderHed.ShipToNum, ShipTo_Row.ShipToNum, true) == 0
                          select ShipTo_Row).FirstOrDefault();
                if (ShipTo != null)
                {
                    delivdays = ShipTo.DemandDeliveryDays;
                    daysearly = (decimal)ShipTo["Number01"];
                    shipbymod = ((int)delivdays + (int)daysearly);
                    newShipDate = SubtractBusinessDays(ttorderdtlRow.NeedByDate.Value, shipbymod);
                    ttorderdtlRow.RequestDate = newShipDate;
                }
        }
    }
}
public static DateTime AddBusinessDays(DateTime current, int days)
{
    var sign = Math.Sign(days);
    var unsignedDays = Math.Abs(days);
    for (var i = 0; i < unsignedDays; i++)
    {
        do
        {
            current = current.AddDays(sign);
        }
        while (current.DayOfWeek == DayOfWeek.Saturday ||
            current.DayOfWeek == DayOfWeek.Sunday);
    }
    return current;
}
//





<summary>
// Subtracts the given number of business days to NeedByDate.
// <var name="current">The date to be changed.
// <var name="days">Number of business days to be subtracted.
// returns <see NeedByDate> increased by a given number of business days.
public static DateTime SubtractBusinessDays(DateTime current, int days)
{
    return AddBusinessDays(current, -days);
}

D & S BO Reader

This is a customization that demonstrates the use of BO Reader. BO Reader is a library that can be used to access a reduced dataset from any (most) business objects. We are making the entire script available, however the most interesting portion regarding BO Reader we’ve decorated with /*******/ comments to make it easier to find.

public class Script
{
    // ** Wizard Insert Location - Do Not Remove 'Begin/End Wizard Added Module Level Variables' Comments! **
    // Begin Wizard Added Module Level Variables **
    private EpiDataView edvEnd;
  
  	/********
    DECLRE A CLASS LEVEL VARIABLE OF TYPE BOREADER
    *********/
    private Ice.Proxy.Lib.BOReaderImpl _bor;
    // End Wizard Added Module Level Variables **
    // Add Custom Module Level Variables Here **
    public void InitializeCustomCode()
    {
        // ** Wizard Insert Location - Do not delete 'Begin/End Wizard Added Variable Initialization' lines **
        // Begin Wizard Added Variable Initialization
        this.edvEnd = ((EpiDataView)(this.oTrans.EpiDataViews["End"]));
        this.edvEnd.EpiViewNotification += new EpiViewNotification(this.edvEnd_EpiViewNotification);
      
      /******
      INSTANCIATE THE BO READER WCF SERVICE
      ******/
        _bor = WCFServiceSupport.CreateImpl<Ice.Proxy.Lib.BOReaderImpl>((Ice.Core.Session)oTrans.Session, Epicor.ServiceModel.Channels.ImplBase<Ice.Contracts.BOReaderSvcContract>.UriPath);
        // End Wizard Added Variable Initialization
        // Begin Wizard Added Custom Method Calls
        this.btnOK_c.Click += new System.EventHandler(this.btnOK_c_Click);
        // End Wizard Added Custom Method Calls
    }
    public void DestroyCustomCode()
    {
        // ** Wizard Insert Location - Do not delete 'Begin/End Wizard Added Object Disposal' lines **
        // Begin Wizard Added Object Disposal
        this.edvEnd.EpiViewNotification -= new EpiViewNotification(this.edvEnd_EpiViewNotification);
        this.edvEnd = null;
      	/******
        ALWAYS BE A GOOD CITIZEN AND DISPOSE OF YOUR OBJECTS
        *****/
        _bor = null;
        this.btnOK_c.Click -= new System.EventHandler(this.btnOK_c_Click);
        // End Wizard Added Object Disposal
        // Begin Custom Code Disposal
        // End Custom Code Disposal
    }
    private void btnOK_c_Click(object sender, System.EventArgs args)
    {
        // ** Place Event Handling Code Here **
        EpiNumericEditor intQty = (EpiNumericEditor)csm.GetNativeControlReference("f1fdcf24-066d-424e-9b59-319a804854fa");
        EpiNumericEditor intSetup = (EpiNumericEditor)csm.GetNativeControlReference("31799c26-7331-45b0-a723-9cc464175117");
        if (edvEnd.dataView[edvEnd.Row]["LaborType"].ToString() == "P" & (Convert.ToString(edvEnd.dataView[edvEnd.Row]["JobType"]) == "Manufacture"))
        {
            string strJobNum = edvEnd.dataView[edvEnd.Row]["JobNum"].ToString();
            string intAssy = edvEnd.dataView[edvEnd.Row]["AssemblySeq"].ToString();
            string intOprSeq = edvEnd.dataView[edvEnd.Row]["OprSeq"].ToString();
            string whereclause = "JobNum = '" + strJobNum + "' and" + " AssemblySeq = " + intAssy + " and OprSeq = " + intOprSeq;
            //MessageBox.Show(whereclause);
          
          	/****
            CALL GETROWS on the JobOperSearch Business Object with a where clause and return the RunQty and QtyCompleted Fields only
            ****/
            DataSet dsJobOper = _bor.GetRows("Erp:BO:JobOperSearch", "JobNum = '" + strJobNum + "' and" + " AssemblySeq = " + intAssy + " and OprSeq = " + intOprSeq, "RunQty, QtyCompleted");
            if (dsJobOper.Tables[0].Rows.Count > 0)
            {
                int intRemaining = Convert.ToInt32(dsJobOper.Tables[0].Rows[0]["RunQty"]) - Convert.ToInt32(dsJobOper.Tables[0].Rows[0]["QtyCompleted"]);
                int intRunQty = Convert.ToInt32(dsJobOper.Tables[0].Rows[0]["RunQty"]);
                int intComplete = Convert.ToInt32(dsJobOper.Tables[0].Rows[0]["QtyCompleted"]);
                if (Convert.ToInt32(dsJobOper.Tables[0].Rows[0]["QtyCompleted"]) + Convert.ToInt32(intQty.Value) <= Convert.ToInt32(dsJobOper.Tables[0].Rows[0]["RunQty"])) { //MessageBox.Show(dsJobOper.Tables[0].Rows[0]["QtyCompleted"].ToString()); EpiButton btnOK = (EpiButton)csm.GetNativeControlReference("025fc115-fee3-4973-946e-ba209189d62e"); btnOK.PerformClick(); } else { MessageBox.Show("You attempted to claim " + Convert.ToString(intQty.Value) + " Parts. There have been " + Convert.ToString(intComplete) + " parts previously completed already. You can only claim up to " + Convert.ToString(intRemaining) + " parts. The total required quantity for this operation is " + intRunQty.ToString() + "."); intQty.Value = 0; intQty.Focus(); } } } else if ((Convert.ToString(edvEnd.dataView[edvEnd.Row]["LaborType"]) == "S") & (Convert.ToString(edvEnd.dataView[edvEnd.Row]["JobType"]) == "Manufacture")) { string strJobNum = edvEnd.dataView[edvEnd.Row]["JobNum"].ToString(); string intAssy = edvEnd.dataView[edvEnd.Row]["AssemblySeq"].ToString(); string intOprSeq = edvEnd.dataView[edvEnd.Row]["OprSeq"].ToString(); /**** CALL GETROWS on the JobOperSearch Business Object with a where clause and return the SetupPctComplete field only ****/ DataSet dsJobOperS = _bor.GetRows("Erp:BO:JobOperSearch", "JobNum = '" + strJobNum + "' and" + " AssemblySeq = " + intAssy + " and OprSeq = " + intOprSeq, "SetupPctComplete"); if (dsJobOperS.Tables[0].Rows.Count > 0)
            {
                int intSetupRemaining = 100 - Convert.ToInt32(dsJobOperS.Tables[0].Rows[0]["SetupPctComplete"]);
                //MessageBox.Show(dsJobOperS.Tables[0].Rows[0]["SetupPctComplete"].ToString() + " / " + intSetupRemaining.ToString());
                if (Convert.ToInt32(intSetup.Value) >= Convert.ToInt32(dsJobOperS.Tables[0].Rows[0]["SetupPctComplete"]))
                {
                    //MessageBox.Show(dsJobOper.Tables[0].Rows[0]["QtyCompleted"].ToString());
                    EpiButton btnOK = (EpiButton)csm.GetNativeControlReference("025fc115-fee3-4973-946e-ba209189d62e");
                    btnOK.PerformClick();
                }
                else
                {
                    MessageBox.Show("You attempted to claim " + Convert.ToInt32(intSetup.Value) + "% against this setup.  This setup was previously claimed as " + Convert.ToInt32(dsJobOperS.Tables[0].Rows[0]["SetupPctComplete"]) + "% complete.  You must claim more than the previous value reported.");
                    intSetup.Value = 0;
                    intSetup.Focus();
                }
            }
        }
        else if ((Convert.ToString(edvEnd.dataView[edvEnd.Row]["LaborType"]) == "I"))
        {
            EpiButton btnOK = (EpiButton)csm.GetNativeControlReference("025fc115-fee3-4973-946e-ba209189d62e");
            btnOK.PerformClick();
        }
        else if ((Convert.ToString(edvEnd.dataView[edvEnd.Row]["LaborType"]) == "S") & (Convert.ToString(edvEnd.dataView[edvEnd.Row]["JobType"]) == "Maintenance"))
        {
            if (Convert.ToInt32(intQty.Value) <= 1)
            {
                EpiButton btnOK = (EpiButton)csm.GetNativeControlReference("025fc115-fee3-4973-946e-ba209189d62e");
                btnOK.PerformClick();
            }
            else
            {
                MessageBox.Show("You can only claim 0 or 1 for Maintenance Operations.");
                intQty.Value = 0;
                intQty.Focus();
            }
        }
    }
}

D & S Call a BAQ from a Customization

This is a customization that demonstrates calling a BAQ in code, this can be a very useful way of querying the database from the Client side. We have once again decorated the relevant code with /*****/ comments

public class Script
{
    // ** Wizard Insert Location - Do Not Remove 'Begin/End Wizard Added Module Level Variables' Comments! **
    // Begin Wizard Added Module Level Variables **
    // End Wizard Added Module Level Variables **
    // Add Custom Module Level Variables Here **
    private EpiDataView edvStart;
    private Ice.Proxy.Lib.BOReaderImpl _bor;
  	/*****
    Declare a variable of type DynamicQueryImpl
    ****/
    private DynamicQueryImpl dq;
    public void InitializeCustomCode()
    {
        // ** Wizard Insert Location - Do not delete 'Begin/End Wizard Added Variable Initialization' lines **
        // Begin Wizard Added Variable Initialization
        // End Wizard Added Variable Initialization
        // Begin Wizard Added Custom Method Calls
        this.btnOK_c.Click += new System.EventHandler(this.btnOK_c_Click);
        // End Wizard Added Custom Method Calls
        edvStart = ((EpiDataView)(this.oTrans.EpiDataViews["Start"]));
        _bor = WCFServiceSupport.CreateImpl<Ice.Proxy.Lib.BOReaderImpl>((Ice.Core.Session)oTrans.Session, Epicor.ServiceModel.Channels.ImplBase<Ice.Contracts.BOReaderSvcContract>.UriPath);
      
      	/*****
    		Instanciate our DynamicQuery variable
    		****/
        dq = WCFServiceSupport.CreateImpl<Ice.Proxy.BO.DynamicQueryImpl>((Session)oTrans.Session, Epicor.ServiceModel.Channels.ImplBase<Ice.Contracts.DynamicQuerySvcContract>.UriPath);
    }
    public void DestroyCustomCode()
    {
        // ** Wizard Insert Location - Do not delete 'Begin/End Wizard Added Object Disposal' lines **
        // Begin Wizard Added Object Disposal
        this.btnOK_c.Click -= new System.EventHandler(this.btnOK_c_Click);
        // End Wizard Added Object Disposal
        // Begin Custom Code Disposal
        _bor.Dispose();
      	/*****
    		Be a good citizen and dispose of your objects!
    		****/
        dq.Dispose();
        // End Custom Code Disposal
    }
    private void btnOK_c_Click(object sender, System.EventArgs args)
    {
        string strJobNum = edvStart.dataView[edvStart.Row]["JobNum"].ToString();
        string strAsmbl = edvStart.dataView[edvStart.Row]["AssemblySeq"].ToString();
        string strOprSeq = edvStart.dataView[edvStart.Row]["OprSeq"].ToString();
        bool currentCall = CheckCurrentOp(strJobNum, strAsmbl, strOprSeq);
        bool prevCall = CheckPrevOp(strJobNum, strAsmbl, strOprSeq);
        //MessageBox.Show("Previous Operation Complete or OverRide:  " + prevCall.ToString());
        //MessageBox.Show("Current Operation Complete:  " + currentCall.ToString());
        if (currentCall == true)
        {
            if (prevCall == true)
            {
                EpiButton btnOK = (EpiButton)csm.GetNativeControlReference("025fc115-fee3-4973-946e-ba209189d62e");
                btnOK.PerformClick();
            }
            else
            {
                MessageBox.Show("You cannot clock into this operation as a previous operation has not been claimed complete that is either an STR or Inspection Operation");
            }
        }
        else
        {
            MessageBox.Show("This operation has already been reported complete.  Please see your supervisor for assistance.");
        }
    }
    private bool CheckCurrentOp(string job, string assembly, string oprseq)
    {
        bool check = false;
        /*****
    		Run get by ID on our DynamicQuery to get a hold of its DynamicQueryDataSet
        this will be used to let the business object know which BAQ we want to excecute
    		****/
        var dqds = dq.GetByID("CheckCurrentOp");
      
      	/*****
    		Run GetQueryExcecutionParametersByID this call gets us the list of any paramters
        that need to be passed to our BAQ for it to run. We then fill those parameters in 
        and execute it
    		****/
        QueryExecutionDataSet qeds = dq.GetQueryExecutionParametersByID("CheckCurrentOp");
      
        /*****
    		Fill in the BAQ parameters that we need to pass in to the BAQ
    		****/
      
        qeds.ExecutionParameter.Clear();
        qeds.ExecutionParameter.AddExecutionParameterRow("JobNum", job, "nvarchar", false, Guid.NewGuid(), "A");
        qeds.ExecutionParameter.AddExecutionParameterRow("Assembly", assembly, "int", false, Guid.NewGuid(), "A");
        qeds.ExecutionParameter.AddExecutionParameterRow("OprSeq", oprseq, "int", false, Guid.NewGuid(), "A");
      
      	/*****
    		Execute the BAQ and get the results back. It returns a dataset with 2 tables "Results" and "Error"
    		****/
        DataSet ds = dq.Execute(dqds, qeds);
        try
        {
            if (ds.Tables["Results"].Rows.Count > 0)
            {
                bool boolOperCmplt = Convert.ToBoolean(ds.Tables[0].Rows[0]["JobOper_OpComplete"]);
                if (boolOperCmplt == false)
                {
                    check = true;
                }
                else
                {
                    check = false;
                    //MessageBox.Show("The previous OpCode is " + strOpCode);
                }
                ds.Dispose();
                dqds.Dispose();
                qeds.Dispose();
            }
            else
            {
                //MessageBox.Show("First Operation");
                check = false;
            }           
        }
        catch
        {
            check = false;
        }
        return check;
    }
    private bool CheckPrevOp(string job, string assembly, string oprseq)
    {
        bool check = false;
        var dqds = dq.GetByID("PreviousOperation");
        QueryExecutionDataSet qeds = dq.GetQueryExecutionParametersByID("PreviousOperation");
        qeds.ExecutionParameter.Clear();
        qeds.ExecutionParameter.AddExecutionParameterRow("JobNum", job, "nvarchar", false, Guid.NewGuid(), "A");
        qeds.ExecutionParameter.AddExecutionParameterRow("AssemblySeq", assembly, "int", false, Guid.NewGuid(), "A");
        qeds.ExecutionParameter.AddExecutionParameterRow("OprSeq", oprseq, "int", false, Guid.NewGuid(), "A");
        DataSet ds = dq.Execute(dqds, qeds);
        try
        {
            if (ds.Tables["Results"].Rows.Count > 0)
            {
                string strOpCode = ds.Tables[0].Rows[0]["JobOper_OpCode"].ToString();
                bool boolPrevCmplt = Convert.ToBoolean(ds.Tables[0].Rows[0]["JobOper_OpComplete"]);
                bool boolOverRide = Convert.ToBoolean(ds.Tables[0].Rows[0]["JobOper_CheckBox06"]);
                if ((string.Compare(strOpCode, "STR", true) == 0 || strOpCode.StartsWith("I")) && boolPrevCmplt == false && boolOverRide == false)
                {
                    check = false;
                }
                else
                {
                    check = true;
                    //MessageBox.Show("The previous OpCode is " + strOpCode);
                }
                ds.Dispose();
                dqds.Dispose();
                qeds.Dispose();
            }
            else
            {
                //MessageBox.Show("First Operation");
                check = true;
            }
            return check;
        }
        catch
        {
            return false;
        }
    }
}