Believe it or not, I spent quite some time tracking down this issue. Definitely not a fun day. Hehe. I have reported it to Sage, but haven't gotten a defect number on it yet, The problem is that the opportunity defaults from the current user are incomplete. When the web client tries to load the data as XML it is failing (BTW, the LAN client loads this data into XML differently so it won't cause this error).
To solve this, simply log into the LAN client (sorry no way to do this in the web). Open the opp defaults screen and then click the OK/Save button to save it. That is it. This will save the XML as an empty set and this will be valid. Here's the write up I did when I reported it to Sage:
--------------------------------------------------------------------
I have a customer who could not bring up the Insert New Opportunity Screen. After stepping through things with VS I determined that I was getting an error in the GetOppProductDefaults business rule on the Opportunity entity. The GetOppProductDefaults method looks like this:
public static void GetOppProductDefaults(IOpportunity opportunity) { string commonOption = ApplicationContext.Current.Services.Get<IUserOptionsService>().GetCommonOption("Products", "OpportunityDefaults"); if (commonOption != "") { commonOption = commonOption.Substring(commonOption.IndexOf("<rs:data>")).Replace("rs:data>", "rsdata>").Replace("z:row", "zrow").Replace("</xml>", ""); XmlDocument document = new XmlDocument(); try { document.LoadXml(commonOption); foreach (XmlNode node in document.SelectSingleNode("rsdata").ChildNodes) { if (node.Attributes["PRODUCTID"].Value != null) { IOpportunityProduct item = EntityFactory.Create<IOpportunityProduct>(); item.Opportunity = opportunity; item.Product = EntityFactory.GetById<IProduct>(node.Attributes["PRODUCTID"].Value); item.Sort = new int?(Convert.ToInt32(node.Attributes["SORT"].Value)); item.Program = node.Attributes["PRICELEVEL"].Value; item.Price = new decimal?(Convert.ToDecimal(node.Attributes["PRICE"].Value)); item.Discount = new double?(Convert.ToDouble(node.Attributes["DISCOUNT"].Value)); item.Quantity = new double?(Convert.ToDouble(node.Attributes["QUANTITY"].Value)); item.ExtendedPrice = new decimal?(Convert.ToDecimal(node.Attributes["EXTENDED"].Value)); opportunity.Products.Add(item); } } } catch (Exception) { throw new Exception("Invalid xml for default Opportunity Products"); } } }
The stack trace from the exception showed the following:
at System.String.InternalSubStringWithChecks(Int32 startIndex, Int32 length, Boolean fAlwaysCopy) at System.String.Substring(Int32 startIndex) at Sage.SalesLogix.Opportunity.Rules.GetOppProductDefaults(IOpportunity opportunity) at Sage.SalesLogix.Opportunity.Rules.CheckOppAccount(IOpportunity opportunity, Boolean& result) at (Object ) at Sage.Platform.DynamicMethod.DynamicMethodLibrary.Execute(String methodName, Object[] args) You'll notice that the exception was thrown from the attempt to use Substring on a string within the GetOppProductDefaults method. The Substring is used to get only the XML between the tags from the serialized XML from the Recordset stored there, ignoring the schema data from the beginning section of the XML. So, I looked and this is what this customer has stored there for *all* users for the Products in the OpportunityDefaults user option:
<xml xmlns:z="#RowsetSchema" xmlns:rs="urn:schemas-microsoft-com:rowset"> <s:Schema id="RowsetSchema"> <s:ElementType name="row" content="eltOnly" rs:updatable="true"> <s:AttributeType name="SORT" rs:number="1" rs:write="true"> <s:datatype dt:type="int" dt:maxLength="4" rs:precision="0" rs:fixedlength="true" rs:maybenull="false"/> </s:AttributeType> <s:AttributeType name="PRODUCTID" rs:number="2" rs:write="true"> <s:datatype dt:type="string" rs:dbtype="str" dt:maxLength="12" rs:precision="0" rs:maybenull="false"/> </s:AttributeType> <s:AttributeType name="KEYFIELDID" rs:number="3" rs:write="true"> <s:datatype dt:type="string" rs:dbtype="str" dt:maxLength="16" rs:precision="0" rs:maybenull="false"/> </s:AttributeType> <s:AttributeType name="PRODUCT" rs:number="4" rs:write="true"> <s:datatype dt:type="string" rs:dbtype="str" dt:maxLength="64" rs:precision="0" rs:maybenull="false"/> </s:AttributeType> <s:AttributeType name="FAMILY" rs:number="5" rs:write="true"> <s:datatype dt:type="string" rs:dbtype="str" dt:maxLength="64" rs:precision="0" rs:maybenull="false"/> </s:AttributeType> <s:AttributeType name="PRICELEVEL" rs:number="6" rs:write="true"> <s:datatype dt:type="string" rs:dbtype="str" dt:maxLength="32" rs:precision="0" rs:maybenull="false"/> </s:AttributeType> <s:AttributeType name="PRICE" rs:number="7" rs:write="true"> <s:datatype dt:type="number" rs:dbtype="currency" dt:maxLength="8" rs:precision="0" rs:fixedlength="true" rs:maybenull="false"/> </s:AttributeType> <s:AttributeType name="DISCOUNT" rs:number="8" rs:write="true"> <s:datatype dt:type="float" dt:maxLength="8" rs:precision="0" rs:fixedlength="true" rs:maybenull="false"/> </s:AttributeType> <s:AttributeType name="ADJPRICE" rs:number="9" rs:write="true"> <s:datatype dt:type="number" rs:dbtype="currency" dt:maxLength="8" rs:precision="0" rs:fixedlength="true" rs:maybenull="false"/> </s:AttributeType> <s:AttributeType name="PRICELOCAL" rs:number="10" rs:write="true"> <s:datatype dt:type="number" rs:dbtype="currency" dt:maxLength="8" rs:precision="0" rs:fixedlength="true" rs:maybenull="false"/> </s:AttributeType> <s:AttributeType name="QUANTITY" rs:number="11" rs:write="true"> <s:datatype dt:type="float" dt:maxLength="8" rs:precision="0" rs:fixedlength="true" rs:maybenull="false"/> </s:AttributeType> <s:AttributeType name="EXTENDED" rs:number="12" rs:write="true"> <s:datatype dt:type="number" rs:dbtype="currency" dt:maxLength="8" rs:precision="0" rs:fixedlength="true" rs:maybenull="false"/> </s:AttributeType> <s:AttributeType name="EXTENDEDLOCAL" rs:number="13" rs:write="true"> <s:datatype dt:type="number" rs:dbtype="currency" dt:maxLength="8" rs:precision="0" rs:fixedlength="true" rs:maybenull="false"/> </s:AttributeType> <s:extends type="rs:rowbase"/> </s:ElementType> </s:Schema> </xml> There is no <rs:data> section in this XML, so obviously, that is why the use of Substring is failing, because the IndexOf is returning an invalid integer for a start position. Normally, in any other system I've checked so far, this XML is completely blank for users that have never set any defaults. For a user that goes into the dialog to set the defaults and clicks OK without selecting any default products they will have the XML, but an empty <rs:data> section. Empty is OK, because at least it exists and the code above will not fail. So, I have no idea what caused the XML to get saved this way in the first place. I will continue to look for this in other systems, but I wanted to mention all this here in case others run into this. This is all fixed by having these users simply go into the Opportunity Defaults dialog (in the LAN client) and just clicking OK. This will cause the empty <rs:data> section to be added to the XML for the serialized recordset and then all wil work fine. I was originally going to suggest that the code for the GetOppProductDefaults be changed to check the IndexOf the "<rs:data>" section frst before proceeding to get the Substring. This certainly would have saved me some headache since the code would not have completely crapped out for this customer - but I have no idea how this got stored this way for this customer in the first place, so no telling how common this scenario would be. The weird thing is that the XML is like this for *all* users for my customer. This was an upgrade BTW, but so was my own internal database and it doesn't have this problem.
--------------------------------------------------------------------
Hope that helps, -Ryan |