Programmatically save part configurator (or any form)

So I’ve been trying to save a part configuration programmatically to trigger a data directive on PcInputValue with BO but I couldn’t get a DataSet (ConfigurationSummaryDataSet) to use the saving method of ConfiguratorRuntimeSvcContract.

I found a solution which is very interesting. It opens for me all kind of doors from now on. Thanks to the power of reflection. I hope it is an acceptable way of solving my problem!

I put my code in Configurator User Defined Methods in a method of Client type but I assume it can be used in any form customization. I have tried to customize a form yet.

var oTrans = this.ServerUD.Trans;

//This is why I assume it could work from any customization
var epiBaseForm = oTrans.GetType().GetProperty("EpiBaseForm").GetValue(oTrans,null);
	
var performUpdateMethod = epiBaseForm.GetType().GetMethod("performUpdate",System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);

performUpdateMethod.Invoke(epiBaseForm , new object[]{false});	

@timshuwy

UPDATE:
this code actually causes an error.

What is the ServerUD referencing?

why don’t you create a document rule on your configuration that updates a field in the order Del or Quoted. Then you can create a data directive on either one of these tables to achieve what you need

Sorry for the late answer @mattpeters. It is referencing UserDefinedFunctionsLib which has :

    public EpiTransaction Trans;
    public Guid TestID;
    public string ConfigID;
    private ConfigurationRuntimeExtImpl configurationExtBO;
    public event UserDefinedFunctionsLib.UpdateDataFromServer OnUpdateDataFromServer; 

@Christian_Pouchoulen
Will the document rule save the input values in PcInputValues ?

Here is what you have to do. On this example, I created a Data Directive for the pcValueSet table and are saving all inputs in a comments field in the sales order line. The table that keeps all cfg inputs is not available to BPMs / BAQs. I hope this helps you

var pcValueSet = (from row in ttPcValueSet where row.Company == Session.CompanyID select row).FirstOrDefault();
if(pcValueSet != null)
  {
    string strComments = "";
    string strXML = pcValueSet.FieldValues;
    
    var dbPCValueGrp = (from row in Db.PcValueGrp 
                        where row.Company == Session.CompanyID && row.GroupSeq == pcValueSet.GroupSeq && row.HeadNum == pcValueSet.HeadNum && row.RelatedToTableName == "OrderDtl"
                        select row).FirstOrDefault();
    if(dbPCValueGrp != null)
    {     
       System.Xml.Linq.XDocument doc = System.Xml.Linq.XDocument.Parse(strXML);
       IEnumerable<System.Xml.Linq.XElement> fieldVal = from el in doc.Descendants() select el;   
                             
        foreach (System.Xml.Linq.XElement f in fieldVal)
        {
          if((f.Name.ToString().Contains("ComboBox") || f.Name.ToString().Contains("TextBox")) && f.Value != "")
          {
            
            string strName = "";            
            int iPosition = strXML.IndexOf("fieldRule:" + f.Name);            
            string []sArr = strXML.Substring(iPosition, 100).Split('|');
            if(sArr.Length > 1)
            {   
              strName = sArr[1];              
            } 
            else
            {
              strName = f.Name.ToString();
            }
            strComments += strName + ": " + f.Value + System.Environment.NewLine;
          }
        }       
        
        var dbOrderDtl = (from row in Db.OrderDtl where row.Company == Session.CompanyID && row.SysRowID == dbPCValueGrp.RelatedToSysRowID select row).FirstOrDefault();
        if(dbOrderDtl != null)
          {
              dbOrderDtl.OrderComment = strComments;
              Db.Validate();
          }
                           
    }
 }
1 Like

@Christian_Pouchoulen thanks for your quick reply I did something similar to that But I really needed to have that field saved in PcInputValues which are actually accessible via External BAQ :slight_smile: but is not modifiable since it is filled by stored procedures I think.

Here is my code, It’s not that bad but in this case I only have one field to change and save. But if I have a lot lot more I would prefer a Different solution. So by changing the xml string, the stored procedures do what I need and I get my data directive on PcInputValues triggered.

var groupSeq = Context.OrderNumber > 0 ? System.Convert.ToInt32(Inputs.orderGroupSeq.Value) : System.Convert.ToInt32(Inputs.quoteGroupSeq.Value);

try {
using(var txScope = IceContext.CreateDefaultTransactionScope()) {

var inputValues = Db.PcValueSet.Where(pc => pc.ConfigID == Context.ConfigurationID && pc.GroupSeq == groupSeq).FirstOrDefault();

var xml = inputValues.FieldValues;

var doc = new System.Xml.XmlDocument();

doc.LoadXml(xml);

doc.DocumentElement.SelectNodes("//_CfgPage1Table/_CfgConfiguratorPage1Row/status").Item(0).InnerText = "Configuring";

doc.DocumentElement.SelectNodes("//_CfgConfiguratorPage1Table/_CfgConfiguratorPage1Row/manualRequestMessage").Item(0).InnerText = Inputs.manualRequestMessage.Value;

inputValues.FieldValues = doc.OuterXml;

Db.Validate();

txScope.Complete();
}
} catch (Exception e) {
Epicor.Customization.Bpm.InfoMessage.Publish("There was an error trying to update the state. Please press Save button to try again.", Ice.Common.BusinessObjectMessageType.Error, Ice.Bpm.InfoMessageDisplayMode.Individual);
}
1 Like

The table is available and readable in 10.2.500 with a BAQ. You can use the code below to loop through all your controls and values. In any version you can access these tables this way and can access in a normal BAQ if on 10.2.500. If you are on a sub 10.2.500 version you can use an external BAQ that uses PCInputValue without doing anything special to see the fields since it is a view over these tables.

foreach (var PCValueSetRecord in (from row in Db.PcValueSet where row.GroupSeq == GroupSeq
select row))
{
  string XMLValue = PCValueSetRecord.FieldValues;
  XmlReader xr = XmlReader.Create(new StringReader(XMLValue));
  var xMembers = from members in XElement.Load(xr).Elements() select members;
  var children = xMembers.Elements();
  var AllElements = children.Where(e => e.Value != string.Empty).Select(e => new 
  {
    Name = e.Name.ToString(), // Control Name
    Value = e.Value.ToString(), // Input Value
    Type = (string)e.Attribute("Type") // Type (i.e. System.Int32)
  }).ToList();
2 Likes

Hello @danbedwards thanks for your answer however I dont have any difficulty getting data from a configuration. I am already using am external BAQ for PcInputValues. I want to trigger the save button of the toolbar. I found the code in EpiBaseForm that does that but when I call it with reflection I get an error.

So you just are looking to mimic what the toolbar Save is doing in a Button click?

yes I was able to save indirectly to PcInputValues through the xml string of input values in PcValuesSet but I don’t really like this solution. if I have lets say 100 inputs it’s a lot of work. I haven’t investigated yet the infragistics toolbar manager properly. Maybe I’ll find my answer there.

Sorry, I am not following. Why not just click the save button?

Because I’m doing stateful processing and I cannot trust the user that he would click save. That’s why I want to programmatically save the configurator.

Do you provide an undo/redo stack in case the user chose the wrong option?

So instead of doing it this way we usually add a validation during the on save that validates the configuration is complete and passes the flag to the order line. If that flag is not set, you prevent future activity on this line until the configuration is complete. People learn in a hurry not to just close the configurator - and the more inputs the faster they learn that.

1 Like

@Mark_Wonsil Yes I do have that option. Everytime the state changes, I save the configurator by updating PcValueSet. The user can cancel the Generation of a drawing, or Revise it when its ready.

@danderson The problem is that I have two states, I already have a validation for the inputs. The other state I’m worried of is the one that processes things outside of Epicor. It’s related to drawing generation with SolidWorks.

For more details, I have 4 states. Configuring, Generating(automated drawing), Manual Request,
and Drawing Ready

Are you using the configurator as the repository for the current state?

Are there no performance penalties for that many revisions?

No basically the configurator reflects the current state that is saved with other informations in an UD Table.

I don’t see any performance issue with revising a drawing multiple times.

So we do this same thing quite often with different engines - Solidworks actually being one of them. We configure, and set the flag to trigger the BPM that can then send the inputs to the rendering engine. The rendering, once complete, is then attached back to the order or quote via REST from the rendering engine and the user is notified that the drawing is ready for sending to customer using a case task. We have at least 4 different similar scenarios like this and all work great just with validation flags. Sounds similar to what you are trying to do.

2 Likes

@danbedwards it is very similar. I haven’t explored how Epicor Rest works. But I have a BPM that calls a server which does everything. If there is a modifcation do to in Epicor I use WCF, like attachments or firming a Job etc. When the Drawing is approved and the order is created, the MRP process creates a job for my custom part. Then, I set one Engineering Method blueprint and modify the quantities, parts, lengths according to what the engine calculated…

So lets say the user Cancels a generation from the configurator but he doesn’t save. The State wont change and the process would continue. I try to minimise the work of Sales representatives. Clicking as less as possible. And I always follow that rule: Never trust the user lol