AX 2012: Existing DIXF service for new AOS instance

Product:

Dynamics AX 2012

Purpose:

The purpose of this document is to explain what we can do to reuse a DIXF service of an existing AOS instance for a new AOS instance

Steps:

Copy paste the following files from the bin folder of an existing AOS instance to the bin folder of the new AOS instance:

  • DMFClientConfig.xml
  • DMFConfig.xml
  • Microsoft.Dynamics.AX.Framework.Tools.DMF.ServiceProxy.dll.config
Advertisements

D365: Send email in X++ using email templates

Product:

Dynamics 365 for Finance and Operations

Purpose:

The purpose of this document is to demonstrate how we can send emails in X++ using built-in email templates. For demonstration purposes we’ll be using standard email template CnfmOrder. This email template is used to send email to customer when a sales order is confirmed.

Business requirement:

Automate sending email to customer’s contact when a sales order is confirmed.

Prerequisites:

Email parameters must be configured.

Development:

You can find built-in email templates in the system under:

Organization administration > Setup > Email templates

Note that we have keyed in the sender email field as it will be used in the code as the “Sender”.

image

You can use the following code to automatically send email using X++. The given code also handles inserting placeholder values in the email template. It uses salesId variable which is a global class member and is provided by the caller class.

public void generateEmail()
{
   // Tokens for email template replacement
   #define.SalesIdToken('salesid')
   #define.CustNameToken('customername')
   #define.DeliveryAddress('deliveryaddress')
   #define.CustomerAddress('customeraddress')
   #define.ShipDate('shipdate')
   #define.ModeOfDelivery('modeofdelivery')
   #define.Charges('charges')
   #define.Discount('discount')
   #define.Tax('tax')
   #define.Total('total')
   #define.LineProductName('lineproductname')
   #define.LineProductDesc('lineproductdescription')
   #define.LinePrice('lineprice')
   #define.LineQuantity('linequantity')
   #define.LineNetAmount('linenetamount')

   SalesTable salesTable;
   SalesLine salesLine;
   CustTable custTable;
   ContactPerson contactPerson;
   SalesTotals salesTotals;
   SysEmailId emailId;
   Map templateTokens;

   str emailSenderName;
   str emailSenderAddr;
   str emailSubject;
   str emailBody;
   str emailToAddress;

   salesTable = SalesTable::find(salesId);
   salesLine = SalesLine::find(salesTable.SalesId);
   custTable = CustTable::find(salesTable.CustAccount);
   contactPerson = ContactPerson::find(salesTable.SixCustContactPersonId);
   salesTotals = SalesTotals::construct(salesTable);
   emailToAddress = contactPerson.email();
   emailId = salesTable.SixSysEmailId;

   templateTokens = new Map(Types::String, Types::String);
   templateTokens.insert(#SalesIdToken, salesTable.SalesId);
   templateTokens.insert(#CustNameToken, custTable.name());
   templateTokens.insert(#DeliveryAddress, salesTable.deliveryAddress().Address);
   templateTokens.insert(#CustomerAddress, custTable.address());
   templateTokens.insert(#ShipDate, strFmt("%1", salesTable.ShippingDateRequested));
   templateTokens.insert(#ModeOfDelivery, strFmt("%1", salesTable.DlvMode));
   templateTokens.insert(#Charges, strFmt("", salesTotals.totalMarkup()));
   templateTokens.insert(#Discount, strFmt("", salesTotals.totalEndDisc()));
   templateTokens.insert(#Tax, strFmt("", salesTotals.totalTaxAmount()));
   templateTokens.insert(#Total, strFmt("", salesTotals.totalAmount()));
   templateTokens.insert(#LineProductName, salesLine.itemName());
   templateTokens.insert(#LineProductDesc, salesLine.itemLineDisc());
   templateTokens.insert(#LinePrice, strFmt("%1", salesLine.SalesPrice));
   templateTokens.insert(#LineQuantity, strFmt("%1", salesLine.SalesQty));
   templateTokens.insert(#LineNetAmount, strFmt("%1", salesLine.LineAmount));
      
   if (emailId)
   {
    [emailSubject, emailBody, emailSenderAddr, emailSenderName] =
      SixCustVendEmailController::getEmailTemplate(emailId, custTable.languageId());
   }
      
   var messageBuilder = new SysMailerMessageBuilder();
   messageBuilder.addTo(emailToAddress)
    .setSubject(emailSubject)
    .setBody(SysEmailMessage::stringExpand(
      emailBody, SysEmailTable::htmlEncodeParameters(templateTokens)));

   if (emailSenderAddr)
   {
    messageBuilder.setFrom(emailSenderAddr, emailSenderName);                
    SysMailerFactory::getNonInteractiveMailer().sendNonInteractive(messageBuilder.getMessage());
   }
}

The generateEmail() method calls the following method to get the email template:

protected static container getEmailTemplate(SysEmailId _emailId, LanguageId _languageId)
{
   var messageTable = SysEmailMessageTable::find(_emailId, _languageId);
   var emailTable = SysEmailTable::find(_emailId);

   if (!messageTable && emailTable)
   {
      // Try to find the email message using the default language from the email parameters
      messageTable = SysEmailMessageTable::find(_emailId, emailTable.DefaultLanguage);
   }

   if (messageTable)
   {
      return [messageTable.Subject, messageTable.Mail, emailTable.SenderAddr, emailTable.SenderName];
   }
   else
   {
      warning("@SYS135886"); // Let the user know we didn't find a template
      return ['', '', emailTable.SenderAddr, emailTable.SenderName];
   }
}

Once triggered, the code sends out the email through the configured SMTP server and can be found in the inbox:

D365: Create lookup in X++

Product:

Dynamics 365 for Finance and Operations

Purpose:

The purpose of this document is to demonstrate how we can create a lookup in X++ and attach it to an extension field added to the form extension of standard Sales order form. This is a good example to see how we can use event handling to achieve our goal without modifying the standard code.

Business requirement:

Display contacts of the customer in a lookup for which the sales order has been raised.

Development:

You can find the event handler below which handles the lookup event of a form control. This form control is added to the extension of SalesTable standard AX form.

[FormControlEventHandler(formControlStr(SalesTable, SixSalesOrderConfirmation_SixCustContactPersonId),
FormControlEventType::Lookup)]
public static void SixSalesOrderConfirmation_SixCustContactPersonId_OnLookup(FormControl sender, FormControlEventArgs e)
{
Query query;
QueryBuildDataSource qbdsContactPerson;
QueryBuildDataSource qbdsCustTable;
QueryBuildDataSource qbdsSalesTable;
SysTableLookup sysTableLookup;
SalesId salesId;
FormControlCancelableSuperEventArgs event;

event = e as FormControlCancelableSuperEventArgs;
salesId = sender.formRun().design().controlName(formControlStr(SalesTable, SalesTable_SalesId)).valueStr();

query = new Query();
qbdsContactPerson = query.addDataSource(tableNum(ContactPerson));

qbdsCustTable = qbdsContactPerson.addDataSource(tableNum(CustTable));
qbdsCustTable.joinMode(JoinMode::InnerJoin);
qbdsCustTable.relations(false);
qbdsCustTable.addLink(
fieldNum(ContactPerson, ContactForParty),
fieldNum(CustTable, Party));

qbdsSalesTable = qbdsCustTable.addDataSource(tableNum(SalesTable));
qbdsSalesTable.joinMode(JoinMode::InnerJoin);
qbdsSalesTable.relations(false);
qbdsSalesTable.addLink(
fieldNum(CustTable, AccountNum),
fieldNum(SalesTable, CustAccount));

qbdsSalesTable.addRange(fieldNum(SalesTable, SalesId)).value(SysQuery::value(salesId));

sysTableLookup = SysTableLookup::newParameters(tableNum(ContactPerson), sender);
sysTableLookup.addLookupfield(fieldNum(ContactPerson, ContactPersonId));
sysTableLookup.addLookupfield(fieldNum(ContactPerson, Party));
sysTableLookup.parmQuery(query);
sysTableLookup.performFormLookup();

event.CancelSuperCall();
}

D365: Reverse customer transaction in X++

Purpose:

The purpose of this document is to demonstrate how we can reverse a posted customer transaction through X++. The code below can be used as a script to automate reversal of posted customer transactions.

Product:

Dynamics 365 for Finance and Operations

Development:

Please find below the code which can be used to reverse a posted customer transaction. It will actually create and post a negative entry transaction against the transaction to reverse. Please note the code defaults reason code to “ERROR” while posting a reversal transaction. Once developed this can be triggered using the following URL:

https://usnconeboxax1aos.cloud.onebox.dynamics.com/?mi=SysClassRunner&cmp=USMF&cls=MAKCustTransReversal

Code:

class MAKCustTransReversal extends TransactionReversal_Cust
{
    public static MAKCustTransReversal construct()
    {
        return new MAKCustTransReversal();
    }

    public boolean showDialog()
    {
        return false;
    }

    public static void main(Args _args)
    {
        CustTrans custTrans;
        MAKCustTransReversal makCustTransReversal;
        ReasonTable reasonTable;
        ReasonCode reasonCode;
        ReasonRefRecID reasonRefRecID;
        InvoiceId invoiceId;
        Args args;
        ;

        invoiceId = "3392";
        reasonCode = "ERROR";        
        reasonTable = ReasonTable::find(reasonCode);
        reasonRefRecID = ReasonTableRef::createReasonTableRef(
            reasonTable.Reason, reasonTable.Description);

        custTrans = CustTrans::findFromInvoice(invoiceId);
            
        if (custTrans.RecId && !custTrans.LastSettleVoucher)
        {
            args = new Args();
            args.record(custTrans);

            makCustTransReversal = MAKCustTransReversal::construct();
            makCustTransReversal.parmReversalDate(systemDateGet());
            makCustTransReversal.parmReasonRefRecId(reasonRefRecID);
            makCustTransReversal.reversal(args);
            
            info(strFmt("%1 %2 %3 %4 reversed.",
                custTrans.Voucher,
                custTrans.TransDate,
                custTrans.Invoice,
                custTrans.Txt));
        }        
    }
}

D365FO: Data entity filter on enum field

To filter data entity using an enum field, we need to use a specific query expression syntax. For example, to filter Purchase order header data entity, PurchaseOrderHeadersV2, to get only ‘Confirmed’ purchase orders, we can add filter to DocumentApprovalStatus entity field which sources its value from VersioningDocumentState enum like below:

https://usnconeboxax1aos.cloud.onebox.dynamics.com/data/PurchaseOrderHeadersV2?$filter=DocumentApprovalStatus eq Microsoft.Dynamics.DataEntities.VersioningDocumentState’Confirmed’

Untitled

As you can see below the query filter successfully filters Purchase order header entity to get only ‘Confirmed’ purchase orders:

Untitled

AX7/D365/Operations: Create customer postal address through data entity from .NET console application

Purpose:

The purpose of this document is to demonstrate how we can create customer postal addresses in Dynamics 365 for Finance and Operations using an external .NET console application.

Code:

static void Main(string[] args)
{
      Program program = new Program();
      Uri oDataUri = new Uri(ODataEntityPath, UriKind.Absolute);
      var context = new Resources(oDataUri);

      context.SendingRequest2 += new EventHandler(delegate (object sender, SendingRequest2EventArgs e)
      {
          var authenticationHeader = OAuthHelper.GetAuthenticationHeader(useWebAppAuthentication: true);
          e.RequestMessage.SetHeader(OAuthHelper.OAuthHeader, authenticationHeader);
      });

      program.testCustomerAddressCreate(context);
}
public void testCustomerAddressCreate(Resources _context)
{
      DataServiceCollection dataServiceCollection = new DataServiceCollection(_context);
      CustomerPostalAddress customerPostalAddress = new CustomerPostalAddress();

      dataServiceCollection.Add(customerPostalAddress);

      customerPostalAddress.CustomerLegalEntityId = "CPL";
      customerPostalAddress.CustomerAccountNumber = "C0000010";
      customerPostalAddress.AddressDescription = "Test address from OData 44.";
      customerPostalAddress.IsRoleBusiness = NoYes.Yes;
      customerPostalAddress.IsPostalAddress = NoYes.Yes;
      customerPostalAddress.AddressLocationRoles = "Address role";
      customerPostalAddress.AddressCountryRegionId = "AUS";
      customerPostalAddress.AddressZipCode = "2151";
      customerPostalAddress.AddressCity = "NORTH ROCKS";
      customerPostalAddress.AddressState = "NSW";

      DataServiceResponse response = null;

      try
      {
          response = _context.SaveChanges(SaveChangesOptions.PostOnlySetProperties);
      }
      catch (Exception ex)
      {
          Console.WriteLine(ex.Message + ex.InnerException);
      }

      Console.ReadLine();
}

AX7/D365/Operations: Enable/Disable form control in X++

Purpose:

The purpose of this document is to demonstrate how we can enable/disable a form control in X++ based on a business logic.

Product:

Dynamics 365 for Finance and Operations, Platform Update 9.

Development approach:

Customization through extension.

Development:

  1. Create a post event handler of a standard form method enabling/disabling the form controls. In this case, we are creating a post event handler of a standard method setFieldAccessHeader of SalesQuotationProjTable form.
  2. Please find the event handler method definition as follows.
[PostHandlerFor(formStr(SalesQuotationProjTable), formMethodStr(SalesQuotationProjTable, setFieldAccessHeader))]
public static void SalesQuotationProjTable_Post_setFieldAccessHeader(XppPrePostArgs args)
{
    FormRun             sender = args.getThis();
    FormDataSource      salesQuotationTable_ds;
    SalesQuotationTable salesQuotationTable;

    salesQuotationTable_ds = sender.dataSource(1);
    salesQuotationTable = salesQuotationTable_ds.cursor();

    sender.control(sender.controlId(formControlStr(SalesQuotationProjTable, DeviceUsageGroup))).enabled((salesQuotationTable.AMDeviceId) ? true : false);
}