The Infolog

A blog of Dynamics AX development tips and tricks

Skip to: Content | Sidebar | Footer

Getting a pending invoice totals

27 November, 2015 (14:37) | Dynamics AX | By: Howard Webb

I needed to create a report which gathered the totals for a pending purchase invoice. Investigating the totals form You can see that it uses the class PurchTotals to calculate the total values. However this uses a PurchParmUpdate record, and at this point a permanent record in the table is not created yet. To get the PurchTotals class to work correctly we would need to create the temp PurchParmUpdate record, calculate the totals and then delete the temporary records. Below is the code to do this. Note that we have to declare the child class of PurchFormLetter to call the correct method:

PurchTable               purchTable;
PurchTotals              purchTotals;
real                     discAmount;
VendInvoiceInfoTable     vendInvoiceInfoTable;
VendInvoice              vendInvoice;
PurchParmUpdate          purchParmUpdate;
PurchFormLetter_Invoice  purchFormLetter;
ParmId                   parmId;

select firstOnly vendInvoiceInfoTable
    where vendInvoiceInfoTable.recID == 5637146096;

purchFormLetter = new PurchFormLetter_Invoice();





parmId = purchFormLetter.parmId();

purchParmUpdate = purchFormLetter.purchParmUpdate();

purchTotals = PurchTotals::newParmTable( vendInvoiceInfoTable, purchParmUpdate.SpecQty, AccountOrder::None, parmId, '', vendInvoiceInfoTable.Ordering);


discAmount = purchTotals.purchTotalAmount();


Creating a configured sales line

15 September, 2015 (09:59) | Dynamics AX | By: Howard Webb

Recently a customer has asked us to develop a way to produce a sales line which contained an item that fully configured via product configurator. For those that are unaware of the process if an item is selected that is configurable you can answer a number of “questions” to allow configuration of an item via the below button on the sales line:

This allows you to customise the item in a number of ways, for example if you are making a sofa which can have a number of coloured threads you can choose the thread colour.

The issue with this is that the configuration form uses .Net to produce the values via some .dll files which we cannot examine. In the end with a lot of debugging we found out that it used XML to get the configuration questions and build the form, then returned XML.

To replicate this we build a class that read the XML, then pulled the answers from a table in a Columbus solution (although this could be any place that stores information).

Firstly we need to make use of a number of framework classes (which will use the default configuration to get the questions) after the sales line has been created:

</p><p><span style="font-family:Consolas; font-size:8pt">    pcConfigurationProductVariantFactory    = pcConfigurationProductVariantFactory::construct();
</span></p><p><span style="font-family:Consolas; font-size:8pt">    vairentContainer                        = pcConfigurationProductVariantFactory.createVariant(ecoResProductMaster, SalesLine.ItemId, SalesLine.InventDimId);  <span style="color:green"><em>//This creates a new inventDIm so will need to update sales line post everything being done</em></span>
    </span></p><p><span style="font-family:Consolas; font-size:8pt">    newInventDimId                          = <span style="color:darkblue"><strong>conPeek</strong></span>(vairentContainer, <span style="color:red"><strong>2</strong></span>);
</span></p><p><span style="font-family:Consolas; font-size:8pt">    pcVariantConfiguration                  = pcVariantConfiguration::find(<span style="color:darkblue"><strong>conPeek</strong></span>(vairentContainer, <span style="color:red"><strong>1</strong></span>));  <span style="color:green"><em>//gets the new varient record</em></span>
    </span></p><p><span style="font-family:Consolas; font-size:8pt">    productModelVersion                     = PCProductModelVersion::findActiveVersion(ecoResProductMaster.RecId);
</span></p><p><span style="font-family:Consolas; font-size:8pt">    productConfigurationModel               = productModelVersion.getProductConfigurationModel();
</span></p><p><span style="font-family:Consolas; font-size:8pt">    xmlDocument                             = XmlDocument::newXml(productConfigurationModel.getXML());

The XML document will contain the list of attributes, operation lines and subparts. This will need looping through to get the individual questions. In my example I have used maps to record these items.

With the list of questions generated we can then start to generate the return XML with the questions and answers as per below. In the below example we get the answers from a Columbus solution’s table:

 </p><p><span style="font-family:Consolas; font-size:8pt"><span style="color:darkblue"><strong>private</strong></span>
      <span style="color:darkblue"><strong>void</strong></span> createAttributeElements()
</span></p><p><span style="font-family:Consolas; font-size:8pt">{
</span></p><p><span style="font-family:Consolas; font-size:8pt">    OSSProfileFieldValue        ossProfileFieldValue;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    OSSProfileField             ossProfileField;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    String255                   questionName, questionDisplayName, questionType;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    XmlNode                     attributeNode;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    XmlAttribute                attributeNameAttribute, attributeIdAttribute, attributeSelectedAttribute, attributeTypeAttribute, attributeValueAttribute;
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>int</strong></span>                         numOfDec;
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>utcDateTime</strong></span>                 dateTime;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    TimeHour24                  emptyTime;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    InventSizeConversionTable   inventSizeConversionTable;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    ;
 </p><p> </p><p><span style="font-family:Consolas; font-size:8pt">    mapEnumerator               = attributes.getEnumerator();
 </p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>while</strong></span>(mapEnumerator.moveNext())
</span></p><p><span style="font-family:Consolas; font-size:8pt">    {
 </p><p><span style="font-family:Consolas; font-size:8pt">        questionDetails             = mapEnumerator.currentValue();
</span></p><p><span style="font-family:Consolas; font-size:8pt">        questionName                = <span style="color:darkblue"><strong>conPeek</strong></span>(questionDetails, <span style="color:red"><strong>1</strong></span>);
</span></p><p><span style="font-family:Consolas; font-size:8pt">        questionDisplayName         = <span style="color:darkblue"><strong>conPeek</strong></span>(questionDetails, <span style="color:red"><strong>2</strong></span>);
</span></p><p><span style="font-family:Consolas; font-size:8pt">        questionType                = <span style="color:darkblue"><strong>conPeek</strong></span>(questionDetails, <span style="color:red"><strong>3</strong></span>);
</p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>select</strong></span>
      <span style="color:darkblue"><strong>firstOnly</strong></span> RecId <span style="color:darkblue"><strong>from</strong></span> ossProfileField
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>where</strong></span>   ossProfileField.Withdrawn       == NoYes::No
</span></p><p><span style="font-family:Consolas; font-size:8pt">            &amp;&amp;      ossProfileField.EntityTableId   == <span style="color:darkblue"><strong>tableNum</strong></span>(SalesLine)
</span></p><p><span style="font-family:Consolas; font-size:8pt">            &amp;&amp;      ossProfileField.FieldName       == questionName
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>join</strong></span> ossProfileFieldValue
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>where</strong></span>   ossProfileFieldValue.EntityRecId        == salesLine.RecId
</span></p><p><span style="font-family:Consolas; font-size:8pt">                &amp;&amp;      ossProfileFieldValue.EntityTableId      == ossProfileField.EntityTableId
</span></p><p><span style="font-family:Consolas; font-size:8pt">                &amp;&amp;      ossProfileFieldValue.FieldName          == ossProfileField.FieldName;
 </p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>if</strong></span>(ossProfileFieldValue)
</span></p><p><span style="font-family:Consolas; font-size:8pt">        {
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeNode               = configuredXML.createElement   (#attribute);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeNameAttribute      = configuredXML.createAttribute (#nameAttribute);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeIdAttribute        = configuredXML.createAttribute (#uniqueIdAttribute);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeSelectedAttribute  = configuredXML.createAttribute (#isUserSelectedAttribute);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeTypeAttribute      = configuredXML.createAttribute (#typeAttribute);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeValueAttribute     = configuredXML.createAttribute (#valueAttribute);
 </p><p><span style="font-family:Consolas; font-size:8pt">            attributeIdAttribute.       value(MapEnumerator.currentKey());
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeSelectedAttribute. value(<span style="color:darkred">'1'</span>);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeNameAttribute.     value(questionName);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeTypeAttribute.     value(questionType);
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:green"><em>//This area needs further testing</em></span>
    </span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>switch</strong></span>(questionType)
</span></p><p><span style="font-family:Consolas; font-size:8pt">            {
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>case</strong></span> enum2Value(AttributeDataType::Text) :
 </p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>if</strong></span>(questionName == #sizeProperty)
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    {
</span></p><p><span style="font-family:Consolas; font-size:8pt">                        inventSizeConversionTable = inventSizeConversionTable::find(salesLine.itemID, ossProfileFieldValue.StringValue);
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>if</strong></span>(inventSizeConversionTable)
</span></p><p><span style="font-family:Consolas; font-size:8pt">                            attributeValueAttribute.value(inventSizeConversionTable.SizeConversionAXValue);
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>else</strong></span>
    </span></p><p><span style="font-family:Consolas; font-size:8pt">                            attributeValueAttribute.value(ossProfileFieldValue.StringValue);
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    }
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>else</strong></span>
    </span></p><p><span style="font-family:Consolas; font-size:8pt">                        attributeValueAttribute.value(ossProfileFieldValue.StringValue);
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>break</strong></span>;
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>case</strong></span> #enumType :
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    attributeValueAttribute.value(ossProfileFieldValue.StringValue);
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>break</strong></span>;
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>case</strong></span> enum2Value(AttributeDataType::Currency) :
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    attributeValueAttribute.value(ossProfileFieldValue.StringValue);
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>break</strong></span>;
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>case</strong></span> enum2Value(AttributeDataType::Decimal) :
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    numOfDec = numOfDec(ossProfileFieldValue.RealValue);
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    attributeValueAttribute.value(<span style="color:darkblue"><strong>num2str</strong></span>(  ossProfileFieldValue.RealValue,
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:red"><strong>0</strong></span>,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                                            numOfDec,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                                            DecimalSeparator::Auto,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                                            ThousandSeparator::Auto));
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>break</strong></span>;
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>case</strong></span> enum2Value(AttributeDataType::DateTime) :
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    dateTime    = DateTimeUtil::newDateTime(ossProfileFieldValue.DateValue, emptyTime);
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    attributeValueAttribute.value(<span style="color:darkblue"><strong>datetime2str</strong></span>(dateTime, DateFlags::FormatAll));
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>break</strong></span>;
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>case</strong></span> enum2Value(AttributeDataType::Integer) :
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    attributeValueAttribute.value(<span style="color:darkblue"><strong>int2str</strong></span>(ossProfileFieldValue.IntValue));
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>break</strong></span>;
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>case</strong></span> enum2Value(AttributeDataType::TrueFalse) :
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    attributeValueAttribute.value(<span style="color:darkblue"><strong>strfmt</strong></span>(<span style="color:darkred">"%1"</span>, ossProfileFieldValue.BoolValue));
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>break</strong></span>;
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>default</strong></span> :
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>break</strong></span>;
 </p><p><span style="font-family:Consolas; font-size:8pt">            }
 </p><p><span style="font-family:Consolas; font-size:8pt">            attributeNode.attributes().setNamedItem(attributeNameAttribute);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeNode.attributes().setNamedItem(attributeIdAttribute);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeNode.attributes().setNamedItem(attributeSelectedAttribute);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeNode.attributes().setNamedItem(attributeTypeAttribute);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeNode.attributes().setNamedItem(attributeValueAttribute);
 </p><p><span style="font-family:Consolas; font-size:8pt">            componentNode.appendChild(attributeNode);
</span></p><p><span style="font-family:Consolas; font-size:8pt">        }
</span></p><p><span style="font-family:Consolas; font-size:8pt">    }
 </p><p><span style="font-family:Consolas; font-size:8pt">}

This will produce an XML document with the questions and the answers needed. With that XML we can then configure and update the sales line:

</p><p><span style="font-family:Consolas; font-size:8pt"><span style="color:darkblue"><strong>private</strong></span>
      <span style="color:darkblue"><strong>void</strong></span> configureLine()
</span></p><p><span style="font-family:Consolas; font-size:8pt">{
</span></p><p><span style="font-family:Consolas; font-size:8pt">    PCAdaptorSourceDocumentLine             adaptorSourceDocumentLine;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    PCAdaptorFactory                        pcAdaptorFactory;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    PCAdaptor                               pcAdaptor;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    PCAdaptorProductConfigurationModel      adaptorProductConfigurationModel;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    PCXmlParseConfigurationInstance         pcXmlParseConfigurationInstance;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    PCBackEndConfiguration                  pcBackEndConfiguration;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    EcoResDistinctProductVariant            distinctProductVariant;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    PCSourceDocumentLineUtility             sourceDocumentLineUtility;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    InventDim                               inventDim;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    ;
 </p><p><span style="font-family:Consolas; font-size:8pt"><span style="color:white"><strong>    pcAdaptorFactory                    = PCAdaptorFactory::construct();</strong></span>
    </span></p><p><span style="font-family:Consolas; font-size:8pt">    adaptorProductConfigurationModel    = PCAdaptorProductConfigurationModel::construct(productConfigurationModel);
</span></p><p><span style="font-family:Consolas; font-size:8pt">    adaptorSourceDocumentLine           = PCAdaptorSourceDocumentLine::construct(salesLine);
</span></p><p><span style="font-family:Consolas; font-size:8pt">    pcAdaptor                           = pcAdaptorFactory.getAdaptorFromModelName(productConfigurationModel.Name);
 </p><p><span style="font-family:Consolas; font-size:8pt">    pcAdaptor.parmProductConfigurationModel(adaptorProductConfigurationModel);
</span></p><p><span style="font-family:Consolas; font-size:8pt">    pcAdaptor.parmSourceDocumentLine(adaptorSourceDocumentLine);
 </p><p><span style="font-family:Consolas; font-size:8pt">    pcXmlParseConfigurationInstance     = pcXmlParseConfigurationInstance::construct();
 </p><p><span style="font-family:Consolas; font-size:8pt">    pcXmlParseConfigurationInstance.parse(productConfigurationModel, pcVariantConfiguration, <span style="color:darkred">''</span>, configuredXML.xml(), PCRuntimeMode::EditVariant, PCAdaptor);
</span></p><p><span style="font-family:Consolas; font-size:8pt">;
 </p><p><span style="font-family:Consolas; font-size:8pt">    inventDim                           = InventDim::find(newInventDimId);
</span></p><p><span style="font-family:Consolas; font-size:8pt">    pcBackEndConfiguration              = pcBackEndConfiguration::construct();
 </p><p><span style="font-family:Consolas; font-size:8pt">    pcBackEndConfiguration.setup(   pcVariantConfiguration,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                    productModelVersion,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                    salesLine.ItemId,
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>conPeek</strong></span>(vairentContainer, <span style="color:red"><strong>3</strong></span>),
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                    InventDim,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                    InventDim,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                    salesLine.SalesPrice,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                    salesLine.ShippingDateRequested,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                    pcAdaptor,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                    PCRuntimeLibrary::docuGenerateNoYes());
</span></p><p><span style="font-family:Consolas; font-size:8pt"><span style="color:white"><strong>;</strong></span>
 </p><p><span style="font-family:Consolas; font-size:8pt">    InventDim.ConfigId                  = pcBackEndConfiguration.getAppliedConfigurationName();
</span></p><p><span style="font-family:Consolas; font-size:8pt">    inventDim                           = InventDim::findOrCreate(inventDim);
</span></p><p><span style="font-family:Consolas; font-size:8pt">    distinctProductVariant              = pcBackEndConfiguration.getAppliedDistinctProductVariant();
</span></p><p><span style="font-family:Consolas; font-size:8pt">    sourceDocumentLineUtility           = PCSourceDocumentLineUtility::construct();
 </p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>ttsBegin</strong></span>;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    salesLine                   = sourceDocumentLineUtility.updateLineDetails(  salesLine,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                                                                pcBackEndConfiguration.getGeneratedBOMId(),
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                                                                pcBackEndConfiguration.getGeneratedRouteId(),
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                                                                pcAdaptor.getPrice(),
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                                                                salesLine.ShippingDateRequested,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                                                                InventDim.inventDimId);
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>ttsCommit</strong></span>;
</span></p><p><span style="font-family:Consolas; font-size:8pt">}

Using expressions in queries to make complex ranges

23 July, 2015 (15:47) | Dynamics AX | By: Howard Webb

Sometimes we cannot make a query fully represent what we want. For example OR statement can be very difficult. To achieve we can use strings to produce the range as described here. It is also worth remembering that there are a number of methods to help us build ranges, these can be found in the SysQuery and SysQueryRangeUtil classes. Most of these can also be used by the end users in grids and other objects. Here is a Microsoft article on using these that you can pass on.


Below is a job that is creating an OR statement on a query object using the above methods. We use methods on both classes to set our ranges and then use a string to create an or statement:


Storing passwords in AX

23 July, 2015 (13:46) | Dynamics AX | By: Howard Webb

I needed to store a password in AX today and as it has been a while and I forgot, I thought I would post a blog to reference the next time. While there is a password stringEDT we should really be using a CryptoBlob as this will store the password as binary data and will not be readable by a human.

With the field added to our table it is then best to create an edit method on the table for the password allowing ease of use in multiple forms. We will use the standard framework to encrypt (and later decrypt) the password. We will return an empty string if the password is not set, a string of fixed length if there is a password and it has not just been set and finally a string of the correct length if it has just been set in the field:



We will then add the field to the form, remembering to set the PasswordStyle property to yes:




This will then store the password as an encrypted blob in the database.


To unencrypt it we can use the WinAPIServer class again:


password = cryptoblob2str(WinAPIServer::cryptUnProtectData(BobTheBlob));


Adding a range to the query that SalesEditLines form uses

12 June, 2015 (14:41) | Dynamics AX | By: Howard Webb

When posting an invoice, delivery note…etc. from a sales order a common form is used called SalesEditLines:



This form is quite complex and uses several framework classes to build itself and the data within it. Recently I was asked to add a new range to the query that builds the results in the form. To get this took quite some digging in to the classes behind the form. Like most complex forms it has a form class to do the more complex work. The class in this instance is \Classes\SalesEditLinesForm however we also have a number of children class where there can be custom code for each different instance of the form. Which of the children is used depends on the document status of the caller. To find out which is used you can put a breakpoint in \Classes\SalesEditLinesForm\construct.

Not only does this form use the above classes, it also uses the FormLetter framework which is what we are interested here as this will be the bit that creates the query we see within the form. AX will create an instance of \Classes\SalesFormLetter or one of its children again based off the document status. These classes all use a class (or a child of) called \Classes\SalesFormletterParmData.


Within our ParmData class there is a method called queryName, this will contain the AOS query that will be used to create the query. Below is an example of this method for a SalesInvoice:



While we could edit the AOS query, we would need to examine the impact of changing this and we cannot do this dynamically. To change the query via code we can place the code in the method updateQueryBuild. Below is an example found in the class for sales invoice.



We can access the query by using chooseLines.Query() and then change it as we please using the query classes and methods.

Adding fields to PurchPurchaseOrder (Purchase order confirmation)

25 March, 2015 (14:25) | Dynamics AX, SSRS | By: Howard Webb


Sometimes we need to add fields to a report that uses transactional data. Normally these have their own transactional tables such as CustInvoiceJour, however this report is different. If you check the method \Classes\PurchPurchaseOrderDP\createData you can see that the DP class uses PurchLineAllVersions and PurchTableAllVersions to get its data for the report. We will need to change these tables and follow the structure to correctly store data for this report

In this example I will be adding a field to PurchTable, although the process will be the same if you are adding one to PurchLine.

As we are adding it to the header we will need to look at PurchTableAllVersions. Looking at the AOT you can see that PurchTableAllVersions is a view not a table. This view points to a query:



If we look at query you can see it points to two new views:



This views in turn point to new queries, which in turn point to the tables we need:




So currently we have a list of the following objects that will need to be changed:


That’s quite a list for one field, and we are not finished either!


We can modify all of these objects by adding the field to PurchTable and PurchTableHistory. Once this is done remember that you need to add the code to move the new field value from PurchTable to PurchTableHistory by modifying initFromPurchTable. At this point if we check our best practices we see that we also need to create a new parm method on the class AxPurchTable.


With the new method written we can now continue. As the queries PurchTableArchivedVersions and PurchTableNotArchivedVersions have the dynamics field property set to ‘Yes’ our new fields should be there automatically but it is worth checking them by opening up the fields node in the AOT. If you cannot see then restore the query and they should be there.


Next up is to add our fields to the views PurchTableArchivedVersions and PurchTableNotArchivedVersions. Remember you might need to restore the views as well to see the new field.


Unfortunately the query PurchTableAllVersions does not have the dynamics fields set so we have to add these manually. Then finally we can add it to the view \Queries\PurchTableAllVersions.


Finally we can then use the field in our DP class and populate the TMP table. After we have added the field to \Data Dictionary\Tables\PurchPurchaseOrderHeader we can then refresh the SSRS report and use the field.


So our final list of changed objects is:


Running a SSRS report via code

10 March, 2015 (11:22) | Dynamics AX, SSRS | By: Howard Webb

Sometimes we would like to run an SSRS report via code to produce a report without user interaction or to call it during a process. To do this we could call the menu item that launches the report, but more often than not we would like to prepopulate it or call it without user interaction. To call the class based report we need the following items:


  • The report’s controller class or if it does not have one SrsReportRunController: The controller class will control the execution of the report. It also contains the query that will be used to run the report if you need to modify it, you can access it via the getFirstQuery method.
  • The report’s contract class (if it has one): The contract class will have all of the parameters used to run the report. Please note: these could be hidden to the end user so please make sure you examine the class and the SSRS report design.
  • SRSPrintDestinationSettings: this will store the output location, which printer etc.


Below is a job running the System user license count report to screen (it works in all companies and only requires one parameter).



void runReportViaCode(Args _args)


SrsReportRunController controller = new SrsReportRunController();

SysUserLicenseCountRDPContract contract = new SysUserLicenseCountRDPContract();

SRSPrintDestinationSettings printSettings;



controller.parmReportName(ssrsReportStr(SysUserLicenseCountReport, Report)); // Define report and report design to use

controller.parmShowDialog(false); // Use this to hide the report dialog



contract.parmReportStateDate(systemDateGet()); // Set all of the desired paramter values


controller.parmReportContract().parmRdpContract(contract); //Pass the contract class in to the controller


// Get and set the print settings as needed

printSettings = controller.parmReportContract().parmPrintSettings();



controller.startOperation(); //Run the report


“MMANDBA+” rather than file in the AX client

30 January, 2015 (14:48) | Dynamics AX | By: Howard Webb

Recently we have had an issue whereas in the AX client the “File” in the top left drop down was replaced with the text “MMANDBA+”, the tools option was not available and there were a number of other odd errors:




A few co-workers investigated this and found that the issue is down to a corrupt profile and deleting the windows profile will fix it.

Getting the report name for a AX2009 report

8 September, 2014 (15:02) | Dynamics AX | By: Howard Webb


I was on site with a 2009 customer the other day who were starting to develop their own reports. Being quite a few years since I have done it I had forgotten this trick to get the report name that is being run. If you modify the method \Classes\SysReportRun\run and include this line:



You will get the report name.

Reprinting invoices using printJournal

29 August, 2014 (14:12) | Dynamics AX, SSRS | By: Howard Webb

If you need to reprint a sales invoice there is a method on the CustInvoiceJour table called printJournal. Calling this method will print using the original print management destination but you can override the destination by passing in an instance of SalesInvoiceJournalPrint. Below is an example of calling it and forcing the print destination to be the print management destination: