Tuesday 23 October 2018

Create Credit note using X++ AX2012

In the below example we are going to create a credit note for a Sale order which is already invoiced.

For this, we need to create a class and add the below method.
In this method, we are getting the data from a temp table mkCreditNote or any other source like CSV, WCF etc.
public void postCreditnote( RecId               _creditnoteRecid)
{
    CustInvoiceTrans        custInvoiceTrans;
    SalesCopying            salesCopying;
    boolean                 isCreditNoteCreated,isCreditNotePosted;
    SalesId                 salesid;
    SalesTable              salesTable;
    MKCreditNote           mkCreditNote;
    TmpFrmVirtual           tmpFrmVirtualHeader;
    TmpFrmVirtual           tmpFrmVirtualLines;

    void loadTmpVirtualTableAndCreateCN(CustInvoiceTrans _custInvoiceTrans,MKCreditNote _mkCreditNote,SalesTable _salesTable)
    {
        delete_from tmpFrmVirtualLines;
        tmpFrmVirtualLines.TableNum = _custInvoiceTrans.TableId;
        tmpFrmVirtualLines.RecordNo = _custInvoiceTrans.RecId;
        tmpFrmVirtualLines.Id = _custInvoiceTrans.SalesId;
        tmpFrmVirtualLines.LineNum = _custInvoiceTrans.LineNum;
        tmpFrmVirtualLines.TransDate = _custInvoiceTrans.InvoiceDate;
        tmpFrmVirtualLines.Qty = _mkCreditNote.Qty;

        tmpFrmVirtualLines.write();

        salesCopying = SalesCopying::construct(SalesPurchCopy::CreditNoteLines);
        salesCopying.initParameters(_salesTable,
                                    tmpFrmVirtualLines,
                                    tmpFrmVirtualHeader,
                                    1,
                                    true,
                                    false,
                                    true,
                                    true,
                                    false);

        salesCopying.copy();
    }

    select firstOnly salesid, RecId from mkCreditNote
    where mkCreditNote.RecId == _creditnoteRecid;
    if (mkCreditNote.RecId)
    {
        salesTable = SalesTable::find(amsCreditNote.SalesId,false);
        if (salesTable.RecId)
        {
            while select amsCreditNote
                join custInvoiceTrans
                where custInvoiceTrans.SalesId == mkCreditNote.SalesId
                && custInvoiceTrans.ItemId == mkCreditNote.ItemId
                && custInvoiceTrans.InventDimId == mkCreditNote.InventDimId
                && custInvoiceTrans.InventTransId == mkCreditNote.InventTransId
                && amsCreditNote.RecId == _creditnoteRecid
            {
                isCreditNoteCreated = true;
                loadTmpVirtualTableAndCreateCN(custInvoiceTrans,mkCreditNote,salesTable);
            }
        }
    }


}

Monday 8 October 2018

Pass Date and Real (double) datatype from C# to AIF (Sales order)

This is a tricky thing to do. Let's say you are creating a sales order using AIF and you wanted to test it using WSDL from C#.

Everything until this point seems to be very easy, now let's say you have some custom field of type Real and you want to pass a value to that field.

Here comes the trick, for any real or date type variables we need to explicitly specify that we are passing these field values, then only these values will appear in the inbound XML file.

Now how do we do that?
Below is the code sample, as you can see there are two lines for adding MK_PurchPrice. One we will specify that this filed value is going to pass by using MK_PurchPriceSpecified = true and the second line is the actual value. i.e MK_PruchPrice = xxxx

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using createSalesOrderAIF.ServiceReference1;

namespace createSalesOrderAIF
{
    class Program
    {
        static void Main(string[] args)
        {
            var line = new AxdEntity_SalesLine()
            {
                MK_PurchPriceSpecified = true,
                MK_PurchPrice = Convert.ToDecimal(100.20),
                ItemId = "2476619",
                SalesQty = 20,
                SalesUnit = "ea",
                SalesPriceSpecified = true,
                SalesPrice = 200
               
            };

            var order = new AxdEntity_SalesTable()
            {
                CustAccount = "C5001",
                PurchOrderFormNum = "xyz",
                MK_VendAccount = "V1310",
               
                ReceiptDateRequestedSpecified = true,            
                ReceiptDateRequested = DateTime.Now.Date,
                MKTotalGSTSpecified = true,
                MKTotalGST = Convert.ToDecimal(20.10),
                MK_PurchInvoiceId = "Inv_25",
                SalesLine = new AxdEntity_SalesLine[] { line }
            };

            var orderList = new AxdEntity_SalesTable[] { order };
            var callContext = new CallContext() { Company = "ZW01" };
            var client = new SalesOrderServiceClient();

            try
            {
                client.create(callContext, orderList);
                client.Close();
            }
            catch
            {
                client.Abort();
                throw;
            }
        }
    }
}

Thursday 4 October 2018

Using Delegates in the new Dynamics 365 AX (AX 7)

Here we are going to see how we can use the "Delegates".

Background: 
delegates: X++ delegates expose the publisher-subscriber pattern where a delegate defines a clear contract in a publisher class. It is a great way of encapsulating a piece of code. Delegates were introduced in AX 2012 couple of years back. With the new AX release, delegates are the recommended way for customizing standard AX classes.

Example
Now we will see an example of how we can use delegates in the new D365 to develop customizations. 

here we will see how to customise standard AX sales order confirmation process. To keep it simple we will the change value of a field in confirmation journal table. The idea here is how to develop delegates and call them.

Confirmation journal header data is stored in CustConfirmJour table and it is initialised in the below class method during the sales confirmation posting process. Let’s modify it using delegates.




First we create a new delegate method in this class. This serves the purpose of defining a contract between the delegate instance and the delegate handler. There is no business logic inside the delegate method. Also notice that the delegates have return type as Void. In order to access the result value we have to pass EventHandlerResult object as a parameter.
 
 

 
Now we modify the actual method, declare the Event handler result object and call the delegate with the parameters in the method which we need to customise . The only customization in this method is 2 lines of code as highlighted below:
 
 

 
The class structure looks as below:



 Now we create the event handler method and this is where MS has done really nice stuff in moving the AX development environment to Visual Studio. Right click on the delegate method and copy the handler method definition.


Create a new class which will be used to subscribe to the delegate. So we create a new class, let call is salesConfirmJournalExt and paste the copied clipboard text
 
 

The delegate handler definition is automatically added with the below information:
 
 

Now we can add our custom code in this method. I just changed the purchase order field value and added some Infolog. Note that I am actually not returning anything in this method and not using the eventHandlerResult object really.


 So we are done. The see it wokring let's build the solution and confirm a sales order.



During the process the  Infolog messages we added in the delegate handler method are shown 

The confirmation journal has the custom text appended to it in the field we used in our new class method.
 
 

Microsoft strongly recommends to use Delegates for customization due to all the good reasons of having minimum code changes in standard product. So try to use delegates to have a cleaner and manageable solutions.

Monday 1 October 2018

Importing Product Master into AX 2012 using X++ job

//Importing Product Master into AX 2012 using X++ job
static void UploadProductMaster(Args _args)
{
    CommaTextIO         csvFile;
    container                  readCon;
    counter                     icount,inserted;
    Dialog                      dialog;
    DialogField             dfFileName;
    FileName                 fileName;

    InventTable                            inventTable;
    EcoResProduct                      ecoResProduct;
    EcoResProductMaster          EcoResProductMaster;
    EcoResProductTranslation  ecoResProductTranslation;
    InventModelGroupItem        InventModelGroupItemLoc;

    EcoResDistinctProductVariant                    EcoResDistinctProductVariant;
    EcoResProductDimensionGroupProduct  ecoResProductDimensionGroupProduct;
    EcoResStorageDimensionGroupItem         ecoResStorageDimensionGroupItem;
    EcoResTrackingDimensionGroupItem       ecoResTrackingDimensionGroupItem;
    EcoResStorageDimensionGroupProduct   ecoResStorageDimensionGroupProduct;
    EcoResTrackingDimensionGroupProduct ecoResTrackingDimensionGroupProduct;

    InventTableModule           inventTableModule;
    inventItemGroupItem         inventItemGroupItem;
    InventItemSetupSupplyType   inventItemSetupSupplyType;
    ecoResProductIdentifier     ecoResProductIdentifier;


    ItemId                      itemID ;
    str       name,Description,itemtype,productDimensionGroup,StorageDimensionGroup,TrackingDimensiongroup,modelGroupId,itemGroupId,unit,price,searchname;

    inserted =0;
    #File


    dialog = new Dialog("Pick the file");
    dfFileName = dialog.addField(extendedTypeStr("FilenameOpen"));
    dialog.filenameLookupFilter(["All files", #AllFiles]);


    if (dialog.run())
    {
        csvFile = new CommaTextIo(dfFileName.value(), 'r');
        csvFile.inFieldDelimiter(',');
        readCon = csvFile.read();
        ttsBegin;
        while(csvFile.status() == IO_Status::OK)
        {
            readCon = csvFile.read();

            if(readCon)
            {
                icount++;

                itemId                = "ABC9";//conPeek(readCon,1);
                name                  = "ABC9name";//conPeek(readCon,2);
                searchname      = "searchname";
                Description      = "DEscription";
                itemtype            = "BOM";
                productDimensionGroup   = "PG_4";//conPeek(readCon,3);
                StorageDimensionGroup   = "PDG_001";//conPeek(readCon,4);
                TrackingDimensiongroup  = "PDG_001";//conPeek(readCon,5);
                modelGroupId    = "FIFO";//conPeek(readCon,6);
                itemGroupId     = "Television";//conPeek(readCon,7);
                unit            = "ea";//conPeek(readCon,8);
                price           = "0";//conPeek(readCon,9);

                 select firstOnly ecoResProduct where EcoResProduct.DisplayProductNumber == itemId;

                if(!ecoResProduct)
                {
                    EcoResProductMaster.initValue();
                    EcoResProductMaster.DisplayProductNumber = itemId;
                    EcoResProductMaster.SearchName = searchname;
                    EcoResProductMaster.ProductType = EcoResProductType::Item;
                    EcoResProductMaster.VariantConfigurationTechnology = EcoResVariantConfigurationTechnologyType::PredefinedVariants;
                    EcoResProductMaster.insert();
                    ecoResProduct= EcoResProductMaster;
                }

                ecoResProductTranslation.Product = ecoResProduct.RecId;
                ecoResProductTranslation.Name = name;
                EcoResProductTranslation.Description = Description;
                ecoResProductTranslation.setDefaultLanguage();
                ecoResProductTranslation.insert();

                EcoResDistinctProductVariant.DisplayProductNumber = itemId ;
                EcoResDistinctProductVariant.ProductMaster = ecoResProduct.RecId;
                EcoResDistinctProductVariant.ProductType = ecoResProduct.ProductType;
                EcoResDistinctProductVariant.insert();

                inventTable.initValue();
                inventTable.initFromEcoResProduct(ecoResProduct);
                inventTable.ItemId = itemId;
                inventTable.NameAlias = ecoResProduct.SearchName;
                if(ItemType == "BOM")
                    inventTable.PmfProductType = PmfProductType::BOM;
                else
                    inventTable.PmfProductType = PmfProductType::None;
                inventTable.insert(true);

                inventTableModule.initValue();
                inventTableModule.ItemId = inventTable.ItemId;
                inventTableModule.ModuleType = ModuleInventPurchSales::Invent;
                InventTableModule.UnitId = unit;
                InventTableModule.Price = any2real(Price);
                inventTableModule.insert();

                inventTableModule.initValue();
                inventTableModule.ItemId = inventTable.ItemId;
                inventTableModule.ModuleType = ModuleInventPurchSales::Purch;
                InventTableModule.UnitId = unit;
                InventTableModule.Price =  any2real(Price);
                inventTableModule.insert();

                inventTableModule.initValue();
                inventTableModule.ItemId = inventTable.ItemId;
                inventTableModule.ModuleType = ModuleInventPurchSales::Sales;
                InventTableModule.UnitId = unit;
                InventTableModule.Price = any2real(Price);
                inventTableModule.insert();

                //Create inventItemLocation
                InventItemLocation::createDefault(inventTable.ItemId);

                // Creates a new item default order type for the product that is released.

                inventItemSetupSupplyType.initValue();
                inventItemSetupSupplyType.ItemId = inventTable.ItemId;
                inventItemSetupSupplyType.ItemDataAreaId = inventTable.DataAreaId;
                inventItemSetupSupplyType.insert();

                //create relationship tables to dimension groups.

                ecoResStorageDimensionGroupProduct = EcoResStorageDimensionGroupProduct::findByProduct(ecoResProduct.RecId);
                ecoResTrackingDimensionGroupProduct = EcoResTrackingDimensionGroupProduct::findByProduct(ecoResProduct.RecId);

                if (ecoResStorageDimensionGroupProduct.RecId)
                {
                    // mandatory storage dimension group for the product
                    ecoResStorageDimensionGroupItem.ItemDataAreaId = inventTable.DataAreaId;
                    ecoResStorageDimensionGroupItem.ItemId = inventTable.ItemId;
                    ecoResStorageDimensionGroupItem.StorageDimensionGroup = ecoResStorageDimensionGroupProduct.StorageDimensionGroup;
                    ecoResStorageDimensionGroupItem.insert();
                }

                if (ecoResTrackingDimensionGroupProduct.RecId)
                {
                    // mandatory tracking dimension group for the product
                    ecoResTrackingDimensionGroupItem.ItemDataAreaId = inventTable.DataAreaId;
                    ecoResTrackingDimensionGroupItem.ItemId = inventTable.ItemId;
                    ecoResTrackingDimensionGroupItem.TrackingDimensionGroup = ecoResTrackingDimensionGroupProduct.TrackingDimensionGroup;
                    ecoResTrackingDimensionGroupItem.insert();
                }


                InventModelGroupItemLoc.ItemDataAreaId = inventTable.dataAreaId;
                InventModelGroupItemLoc.ItemId = inventTable.ItemId;
                InventModelGroupItemLoc.ModelGroupId =modelGroupId;
                InventModelGroupItemLoc.ModelGroupDataAreaId = curext();
                InventModelGroupItemLoc.initValue();
                InventModelGroupItemLoc.insert();

                 //Item group
                inventItemGroupItem.clear();
                inventItemGroupItem.initValue();
                inventItemGroupItem.ItemDataAreaId = inventTable.dataAreaId;
                inventItemGroupItem.ItemId = inventTable.ItemId;
                inventItemGroupItem.ItemGroupId = itemGroupId;
                inventItemGroupItem.ItemGroupDataAreaId = curext();
                inventItemGroupItem.insert();

                //write product to dimension group relation
                ecoResProductDimensionGroupProduct.initFromProduct(ecoResProduct);
                ecoResProductDimensionGroupProduct.ProductDimensionGroup = EcoResProductDimensionGroup::findByDimensionGroupName(productDimensionGroup).RecId;// "PG_4";
                ecoResProductDimensionGroupProduct.insert();

                ecoResStorageDimensionGroupItem.initValue();
                ecoResStorageDimensionGroupItem.ItemDataAreaId = inventTable.dataAreaId;
                ecoResStorageDimensionGroupItem.ItemId = inventTable.ItemId;
                ecoResStorageDimensionGroupItem.StorageDimensionGroup = EcoResStorageDimensionGroup::findByDimensionGroupName(StorageDimensionGroup).RecId;
                ecoResStorageDimensionGroupItem.insert();

                ecoResTrackingDimensionGroupItem.initValue();
                ecoResTrackingDimensionGroupItem.ItemDataAreaId = inventTable.dataAreaId;
                ecoResTrackingDimensionGroupItem.ItemId = inventTable.ItemId;
                ecoResTrackingDimensionGroupItem.TrackingDimensionGroup = EcoResTrackingDimensionGroup::findByDimensionGroupName(TrackingDimensiongroup).RecId;
                ecoResTrackingDimensionGroupItem.insert();

                ecoResProductIdentifier.clear();
                ecoResProductIdentifier.initValue();
                ecoResProductIdentifier.ProductNumber = itemId;
                ecoResProductIdentifier.Product = EcoResProductMaster.RecId; //ecoResProductMaster.RecId;
                ecoResProductIdentifier.insert();

            }

        }
        ttsCommit;
    }

}


Custom service to import product master using WCF service AX 2012.

In this article, we will see how to import a product into Dynamics AX 2012 using WCF custom service. Use this approach only you need to extream customization otherwise, we can use the standard DIXF Product master for details you can check below link.

 "How to import items master data using Data Migration Framework"


Coming to Custom service to import product master. 

1. We need to create a service class which extends SysOperationServiceBase
class MK_ItemMasterService extends SysOperationServiceBase
{
}

2. Add Entry point method which takes contract class as parameter. 

[SysEntryPointAttribute(true)]
public ItemId createProduct(MK_ItemImportContract _itemImport)
{
      InventTable            inventTable,ret;
    try
    {
        ttsBegin;
        inventTable = InventTable::find(_itemImport.parmProductNumber(),true);
        if (inventTable.RecId)
        {
            //update product
            this.update(_itemImport,inventTable);
        }
        else
        {
            //create new product
            ret = this.create(_itemImport);
        }// end of else
        ttsCommit;
    }
    catch
    {
        return "";
    }

    return ret.itemid;

}

3. Now we can create a Data contract class as below.

[DataContractAttribute]
public class MK_ItemImportContract
{
    Str DisplayProductNumber; // = "Bulb60W1",
    //ProductType = AxdEnum_EcoResProductType.Item,
    str SearchName;// = "Bulb60W1"
    str Name;
    str ProductNumber;
    str StorageDimGroup;
    str ProductDimGroup;
    str TrackingDimGroup;
}

4. Add Data member attribute method for all the variables declared in the class like below.

[DataMemberAttribute]
public str parmStorageDimGroup(Str _StorageDimGroup = StorageDimGroup)
{
    StorageDimGroup = _StorageDimGroup;
    return  StorageDimGroup;
}

5. Now it is the time to create a service 

Goto AOT\Services\ and right click and add new 
Name : MK_ItemMasterService
Class: Service class name (MK_ItemMasterService)
Description: "Product Import Service".
External Name: 

6. Add the Operations to Service.
By Expanding the service and right click on Operations and add
Select the method "CreateProduct" and click ok.

7. Create Service Group 
\Service Groups\MK_ItemMasterServices
Name: MK_ItemMasterServices
AutoDeploy: Yes
Description:

add the above-created service (step 6) to this service group.

8. Deploy the service Group.
Right click on Service Group and deploy.
This will create an Inbound service. 
Copy the WSDL URL.

Now it's time to test the above service. For that, we will create a C# console application.

9. Create a C# console application 
Right click on the references and add a service reference 
Past the URL copied from step 8.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MKProductImportService.ServiceReference1;

namespace MKProductImportService
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                MK_ItemMasterServiceClient client = new MK_ItemMasterServiceClient();
                CallContext context = new CallContext();
                context.Company = "CW01";
                Console.WriteLine("Object Created Success!");

                MK_ItemImportContract procuct = new MK_ItemImportContract();
                procuct.parmDisplayProductNumber = "T00003";
                procuct.parmName = "Test3 Procut WCF";
                procuct.parmProductNumber = "T00003";
                procuct.parmStorageDimGroup = "WL";
                procuct.parmTrackingDimGroup = "BS";
                procuct.parmSearchName = "Test3 WCF";

                Console.WriteLine("Lines added Success!");
                string itemid = client.createProduct(context, procuct); // call the create product method

                Console.WriteLine("Item Created:" + itemid);

                Console.WriteLine("Success! !!!");

            }
            catch (Exception e)
            {
                Console.WriteLine(e.InnerException.Message);
            }
            //Console.ReadLine();
        }
    }
}

10. Build and run 

........................


Update NuGet package to new MS D365FO version

1. Import the NuGet package files from LCS for that particular version please take the PU version files only. a. Goto LCS-->Asset Libra...