Adding material to quote via REST - how do I use default description?

Do you happen to know which custom method?

Run a trace and that will explain it

2 Likes

Okay, I had been adding a new material via the Odata method, were I make a single call:

 request = new RestRequest("api/v1/Erp.BO.QuoteAsmSvc/QuoteMtls", Method.POST);
                    request.AddJsonBody(new { Company = "78863", QuoteNum = quote, QuoteLine = line, AssemblySeq = "0", MtlSeq = CurrenMtlNum.ToString(), PartNum = PlatePart, Description = "Test", QtyPer = plateStats.Total_material_weight1.ToString(), IUM = "Pounds", Class = "Plat", RelatedOperation = CurrentOpNum.ToString() });
                    response = client.Execute(request);

But the trace of adding a material by hand looks like this:


<tracePacket>
  <businessObject>Erp.Proxy.BO.QuoteAsmImpl</businessObject>
  <methodName>GetNewQuoteMtl</methodName>
  <appServerUri>https://ausmtspilot102.epicorsaas.com/SaaS203Pilot/</appServerUri>
  <returnType>System.Void</returnType>
  <localTime>1/7/2019 14:02:13:0350710 PM</localTime>
  <threadID>1</threadID>
  <executionTime total="151" roundTrip="142" channel="0" bpm="0" other="9" />
  <retries>0</retries>
  <parameters>
    <parameter name="ds" type="Erp.BO.QuoteAsmDataSet">
      <QuoteAsmDataSet xmlns="http://www.epicor.com/Ice/300/BO/QuoteAsm/QuoteAsm" />
    </parameter>
    <parameter name="quoteNum" type="System.Int32"><![CDATA[79496]]></parameter>
    <parameter name="quoteLine" type="System.Int32"><![CDATA[11]]></parameter>
    <parameter name="assemblySeq" type="System.Int32"><![CDATA[0]]></parameter>
  </parameters>
</tracePacket>


<tracePacket>
  <businessObject>Erp.Proxy.BO.QuoteImpl</businessObject>
  <methodName>GetPartXRefInfo</methodName>
  <appServerUri>https://ausmtspilot102.epicorsaas.com/SaaS203Pilot/</appServerUri>
  <returnType>System.Void</returnType>
  <localTime>1/7/2019 14:03:07:7966465 PM</localTime>
  <threadID>1</threadID>
  <executionTime total="141" roundTrip="140" channel="0" bpm="0" other="1" />
  <retries>0</retries>
  <parameters>
    <parameter name="partNum" type="System.String"><![CDATA[Plate-Max400-0.375"x96"x240"]]></parameter>
    <parameter name="SysRowID" type="System.Guid"><![CDATA[00000000-0000-0000-0000-000000000000]]></parameter>
    <parameter name="rowType" type="System.String"><![CDATA[]]></parameter>
    <parameter name="uomCode" type="System.String"><![CDATA[]]></parameter>
    <parameter name="serialWarning" type="System.String"><![CDATA[]]></parameter>
    <parameter name="questionString" type="System.String"><![CDATA[]]></parameter>
    <parameter name="multipleMatch" type="System.Boolean"><![CDATA[False]]></parameter>
  </parameters>
</tracePacket>

<tracePacket>
  <businessObject>Erp.Proxy.BO.QuoteAsmImpl</businessObject>
  <methodName>CheckPrePartInfo</methodName>
  <appServerUri>https://ausmtspilot102.epicorsaas.com/SaaS203Pilot/</appServerUri>
  <returnType>System.Void</returnType>
  <localTime>1/7/2019 14:03:07:9372613 PM</localTime>
  <threadID>1</threadID>
  <executionTime total="133" roundTrip="131" channel="0" bpm="0" other="2" />
  <retries>0</retries>
  <parameters>
    <parameter name="partNum" type="System.String"><![CDATA[Plate-Max400-0.375"x96"x240"]]></parameter>
    <parameter name="sourceTable" type="System.String"><![CDATA[QuoteMtl]]></parameter>
    <parameter name="sourceSysRowID" type="System.Guid"><![CDATA[00000000-0000-0000-0000-000000000000]]></parameter>
    <parameter name="vMessage" type="System.String"><![CDATA[]]></parameter>
    <parameter name="vSubAvail" type="System.Boolean"><![CDATA[False]]></parameter>
    <parameter name="vMsgType" type="System.String"><![CDATA[]]></parameter>
    <parameter name="productConfiguratorMessage" type="System.String"><![CDATA[]]></parameter>
  </parameters>
</tracePacket>

<tracePacket>
  <businessObject>Erp.Proxy.BO.QuoteAsmImpl</businessObject>
  <methodName>CheckQuoteMtlPartNum</methodName>
  <appServerUri>https://ausmtspilot102.epicorsaas.com/SaaS203Pilot/</appServerUri>
  <returnType>System.Void</returnType>
  <localTime>1/7/2019 14:03:08:0708296 PM</localTime>
  <threadID>1</threadID>
  <executionTime total="159" roundTrip="138" channel="0" bpm="1" other="20" />
  <retries>0</retries>
  <parameters>
    <parameter name="ds" type="Erp.BO.QuoteAsmDataSet">
      <QuoteAsmDataSet xmlns="http://www.epicor.com/Ice/300/BO/QuoteAsm/QuoteAsm" />
    </parameter>
    <parameter name="ipProposedPartNum" type="System.String"><![CDATA[Plate-Max400-0.375"x96"x240"]]></parameter>
  </parameters>
</tracePacket>

<tracePacket>
  <businessObject>Erp.Proxy.BO.QuoteAsmImpl</businessObject>
  <methodName>GetMtlPartInfo</methodName>
  <appServerUri>https://ausmtspilot102.epicorsaas.com/SaaS203Pilot/</appServerUri>
  <returnType>System.Void</returnType>
  <localTime>1/7/2019 14:03:08:2441363 PM</localTime>
  <threadID>1</threadID>
  <executionTime total="185" roundTrip="173" channel="0" bpm="0" other="12" />
  <retries>0</retries>
  <parameters>
    <parameter name="ds" type="Erp.BO.QuoteAsmDataSet">
      <QuoteAsmDataSet xmlns="http://www.epicor.com/Ice/300/BO/QuoteAsm/QuoteAsm" />
    </parameter>
    <parameter name="partName" type="System.String"><![CDATA[PartNum]]></parameter>
  </parameters>
</tracePacket>

So I tried changing my code to this:

                    request = new RestRequest("api/v1/Erp.BO.QuoteAsmSvc/GetNewQuoteMtl", Method.POST);
                    request.AddJsonBody(new { Company = "78863", quoteNum = quote, quoteLine = line, assemblySeq = "0", ds = new object()});
                    response = client.Execute(request);

                    request = new RestRequest("api/v1/Erp.BO.QuoteSvc/GetPartXRefInfo", Method.POST);
                    request.AddJsonBody(new { partNum = "Plate-Max400-0.375\"x96\"x240\"", SysRowID = "00000000-0000-0000-0000-000000000000", rowType="", uomCode ="", serialWarning ="", questionString ="", multipleMatch ="", ds = new object() });                
                    response = client.Execute(request);

                    request = new RestRequest("api/v1/Erp.BO.QuoteAsmSvc/CheckPrePartInfo", Method.POST);
                    request.AddJsonBody(new { partNum = "Plate-Max400-0.375\"x96\"x240\"", sourceTable= "QuoteMtl", sourceSysRowID = "00000000-0000-0000-0000-000000000000", vMessage = "" , vSubAvail = "False", vMsgType ="", productConfiguratorMessage ="", ds = new object() });
                    response = client.Execute(request);

                    request = new RestRequest("api/v1/Erp.BO.QuoteAsmSvc/CheckQuoteMtlPartNum", Method.POST);
                    request.AddJsonBody(new { ipProposedPartNum = "Plate-Max400-0.375\"x96\"x240\"", ds = new object() });
                    response = client.Execute(request);

                    request = new RestRequest("api/v1/Erp.BO.QuoteAsmSvc/GetMtlPartInfo", Method.POST);
                    request.AddJsonBody(new { partName = "PartNum", ds = new object() });
                    response = client.Execute(request);

But the call to CheckQuoteMtlPartNum and GetMtlPartInfo are failing with the error ErrorMessage":“QuoteMtl record is not avaialble.”

I would guess that there is some kind of result from the previous operations that I need to pass on, but I can’t find an example and the trace doesn’t show any data so I am unsure. How do I figure out what is missing?

I think you need to use the dataset returned by the previous method rather than a new dataset each time. Think of it like a chain reaction that relies on the previous data

2 Likes

Every each rest call returns status OK now, but no material appears in epicor. This is what I have:

                    request = new RestRequest("api/v1/Erp.BO.QuoteAsmSvc/GetNewQuoteMtl", Method.POST);
                    request.AddJsonBody(new { Company = "78863", quoteNum = quote, quoteLine = line, assemblySeq = "0", ds = new object()});
                    response = client.Execute(request);
                    var converter = new ExpandoObjectConverter();
                    dynamic message = JsonConvert.DeserializeObject<ExpandoObject>(response.Content, converter);
                    var ds = message.parameters.ds;
                    request = new RestRequest("api/v1/Erp.BO.QuoteSvc/GetPartXRefInfo", Method.POST);
                    request.AddJsonBody(new { partNum = "Plate-Max400-0.375\"x96\"x240\"", SysRowID = "00000000-0000-0000-0000-000000000000", rowType="", uomCode ="", serialWarning ="", questionString ="", multipleMatch ="", ds });                
                    response = client.Execute(request);

                    request = new RestRequest("api/v1/Erp.BO.QuoteAsmSvc/CheckPrePartInfo", Method.POST);
                    request.AddJsonBody(new { partNum = "Plate-Max400-0.375\"x96\"x240\"", sourceTable= "QuoteMtl", sourceSysRowID = "00000000-0000-0000-0000-000000000000", vMessage = "" , vSubAvail = "False", vMsgType ="", productConfiguratorMessage ="", ds });
                    response = client.Execute(request);
                    request = new RestRequest("api/v1/Erp.BO.QuoteAsmSvc/CheckQuoteMtlPartNum", Method.POST);
                    
                    request.AddJsonBody(new { ipProposedPartNum = "Plate-Max400-0.375\"x96\"x240\"", ds});
                    response = client.Execute(request);
                    dynamic m2 = JsonConvert.DeserializeObject<ExpandoObject>(response.Content, converter);
                    ds = m2.parameters.ds;
                    request = new RestRequest("api/v1/Erp.BO.QuoteAsmSvc/GetMtlPartInfo", Method.POST);
                    request.AddJsonBody(new { partName = "PartNum", ds });
                    response = client.Execute(request);

Any idea what I am doing wrong?

We need to see the actual requests hitting the wire. What does Fiddler Show? Also not sure if the above is C# looks like it. We have a nuget that deals with some of the “epicor stuff” for you and abastracts a lot of the pain of calling rest.
Maybe worth a shot… if you are struggling, it even has visbility onto the RAW calls (ala Fiddler) right on the Class.

EpicorRest.AppPoolHost = "your.tld.server";
EpicorRest.AppPoolInstance = "yourEpicorInstance";
EpicorRest.UserName = "epicor";
EpicorRest.Password = "epicor";
EpicorRest.IgnoreCertErrors = true;

//get
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.Add($"filter", "ABCCode eq 'A'");
dynamic abcData = EpicorRest.DynamicGet("Erp.BO.EmpBasicSvc", "List", dic);

//GetJSON String
string json = EpicorRest.DynamicGetJSON("Erp.BO.EmpBasicSvc", "List", dic);

More info at

1 Like

What do I use for EpicorRest.AppPoolHost and EpicorRest.AppPoolInstance for cloud server? https://ausmtspilot102.epicorsaas.com/SaaS203Pilot/ ?

EpicorRest.AppPoolHost = "ausmtspilot102.epicorsaas.com";
EpicorRest.AppPoolInstance = "SaaS203Pilot";

Thanks. There was nothing wrong with my code, I just did the trace poorly. I didn’t click on another tree item or save after adding the material, so even though it looked like it had been added, there was no Update call in the trace. Once I did another trace I noticed that there was an Update call at the end which wasn’t there before. My code now works.

I did change it to use the EpicorRest Nuget package though. Besides checking the status after each call to see if there was an error, is there anything I can do better in this code?

                    dynamic pData = new { Company = "78863", quoteNum = quote, quoteLine = line, assemblySeq = "0", ds = new object() };
                    dynamic rData = EpicorRest.DynamicPost("Erp.BO.QuoteAsmSvc", "GetNewQuoteMtl", pData);
                    dynamic pData2 = new { partNum = PlatePart, SysRowID = "00000000-0000-0000-0000-000000000000", rowType = "", uomCode = "", serialWarning = "", questionString = "", multipleMatch = "", rData.parameters.ds };
                    dynamic rData2 = EpicorRest.DynamicPost("Erp.BO.QuoteSvc", "GetPartXRefInfo", pData2);
                    dynamic pData3 = new { partNum = PlatePart, sourceTable = "QuoteMtl", sourceSysRowID = "00000000-0000-0000-0000-000000000000", vMessage = "", vSubAvail = "False", vMsgType = "", productConfiguratorMessage = "", rData.parameters.ds };
                    dynamic rData3 = EpicorRest.DynamicPost("Erp.BO.QuoteAsmSvc", "CheckPrePartInfo", pData3);
                    dynamic pData4 = new { ipProposedPartNum = PlatePart, rData.parameters.ds };
                    dynamic rData4 = EpicorRest.DynamicPost("Erp.BO.QuoteAsmSvc", "CheckQuoteMtlPartNum", pData4);
                    dynamic pData5 = new { partName = "PartNum", rData4.parameters.ds };
                    dynamic rData5 = EpicorRest.DynamicPost("Erp.BO.QuoteAsmSvc", "GetMtlPartInfo", pData5);
                    rData5.parameters.ds.QuoteMtl[0]["partNum"] = PlatePart;
                    rData5.parameters.ds.QuoteMtl[0]["QtyPer"] = plateStats.Total_material_weight1.ToString();
                    rData5.parameters.ds.QuoteMtl[0]["IUM"] = "Pounds";
                    rData5.parameters.ds.QuoteMtl[0]["Class"] = "Plat";
                    dynamic pData6 = new { Part = PlatePart, rData5.parameters.ds };
                    dynamic rData6 = EpicorRest.DynamicPost("Erp.BO.QuoteAsmSvc", "Update", pData6);

The data returned from those calls is almost always the same why are you creating Data2,3,4,5,6,tc… you should be able to assign back to the same Object. I think … but other than that it looks fine to me.

OK, I think I have improved it based on your feedback. I made it into a function so I can reuse it. Is there a more secure way to load/store the user credentials for EpicorRest rather than writing them in the source code?

                    bool AddQuoteMtl(string quoteNum, string quoteLine, string assemblySeq, string partNum, double QtyPer, string IUM, string Class, string Operation)
                    {
                        dynamic pData = new { Company = "78863", quoteNum, quoteLine, assemblySeq, ds = new object() };
                        dynamic rData = EpicorRest.DynamicPost("Erp.BO.QuoteAsmSvc", "GetNewQuoteMtl", pData);
                        pData = new { partNum, SysRowID = "00000000-0000-0000-0000-000000000000", rowType = "", uomCode = "", serialWarning = "", questionString = "", multipleMatch = "", rData.parameters.ds };
                        EpicorRest.DynamicPost("Erp.BO.QuoteSvc", "GetPartXRefInfo", pData);
                        if (EpicorRest.LastCallResult != System.Net.HttpStatusCode.OK) return false;
                        pData = new { partNum, sourceTable = "QuoteMtl", sourceSysRowID = "00000000-0000-0000-0000-000000000000", vMessage = "",
                            vSubAvail = "False", vMsgType = "", productConfiguratorMessage = "", rData.parameters.ds };
                        EpicorRest.DynamicPost("Erp.BO.QuoteAsmSvc", "CheckPrePartInfo", pData);
                        if (EpicorRest.LastCallResult != System.Net.HttpStatusCode.OK) return false;
                        pData = new { ipProposedPartNum = partNum, rData.parameters.ds };
                        EpicorRest.DynamicPost("Erp.BO.QuoteAsmSvc", "CheckQuoteMtlPartNum", pData);
                        if (EpicorRest.LastCallResult != System.Net.HttpStatusCode.OK) return false;
                        pData = new { partName = "PartNum", rData.parameters.ds };
                        rData = EpicorRest.DynamicPost("Erp.BO.QuoteAsmSvc", "GetMtlPartInfo", pData);
                        if (EpicorRest.LastCallResult != System.Net.HttpStatusCode.OK) return false;
                        rData.parameters.ds.QuoteMtl[0]["partNum"] = partNum;
                        rData.parameters.ds.QuoteMtl[0]["QtyPer"] = QtyPer.ToString();
                        rData.parameters.ds.QuoteMtl[0]["IUM"] = IUM;
                        rData.parameters.ds.QuoteMtl[0]["Class"] = Class;
                        rData.parameters.ds.QuoteMtl[0]["RelatedOperation"] = Operation;
                        pData = new { Part = PlatePart, rData.parameters.ds };
                        EpicorRest.DynamicPost("Erp.BO.QuoteAsmSvc", "Update", pData);
                        if (EpicorRest.LastCallResult != System.Net.HttpStatusCode.OK) return false;
                        return true;
                    }

Depends on the application, are you talking about calling the API via some custom application? You could store it in an app.config vs. hard coding it and create a “rest client” that consumes the user/pw from the app.config

1 Like

Hey Evan,

Two thoughts here:

1.) Someday, Azure AD will be enabled for SaaS users, then you can just pass your auth token. @josecgomez might have to alter the library to do this if he hasn’t already.

2.) The other “cloudy” way to do this: is Azure Key Vault.

Mark W.

It already supports Bearer token, not sure if the azure token will be “the same” in the end.

1 Like

Yeah, its just a local web app - right now its using my username and password though. I don’t see the difference between storing it in the source code vs a config file from a security perspective though.

Those both sound interesting, but we don’t have any Azure or office 365 products.

If you’re consuming the rest client in multiple places, it would make sense to use an app.config for that rather than writing it everywhere it’s used

Right, its better from a convenience and maintainability perspective, but someone could still just hit ctrl + F + “password” and find my password.

You could encrypt the username/pw in the app.config. Ultimately, you need to decide what level of risk you are willing to take on with this. It’s a balance between maintainability and security. You could always just hard code it in to every client you create, but when happens when the username/password changes? Now you have to change it everywhere in the source code and redeploy.

1 Like

I mean if they have access to your PC, and your source code all bets are off. Security has its limits… If this is something for your company to use, then putting the password in the sysconfig is fine… If this is sothing you are going to distribute out to the word then I wouldn’t embed my password.

1 Like