Generate a New Voucher Number for Each Inventory Journal Line

If you have a business requirement that requires a new voucher number to be generated for each posted inventory movement journal line – that means that you would need to make customization to the out-of-the-box functionality for generating vouchers for inventory journals.

 

Out of the box functionality that we have so far is that a new voucher is being generated on:

 

  • Change date: For each different date in the inventory journal lines.

 

    • Example: If we have an inventory movement journal with 10 lines, 4 lines with date 1/10/2020, and 6 lines with date 4/10/2020, this setup would post the journal and generate 2 voucher numbers: 1 voucher number for the lines with the date 1/10/2020 and 1 voucher number for the lines with the date 4/10/2020.

 

  • Change date or item: For each different date or item in the inventory journal lines.

 

    • Example: If we have an inventory movement journal with 10 lines, 4 lines with item itemnumber1 and 6 lines with item itemnumber2 and all lines having the same date, then this setup would post the journal and generate 2 voucher numbers: 1 voucher number for the lines with the item itemnumber1 and 1 voucher number for the lines with the item itemnumber2.

 

This is defined in the setup of the inventory journal name:

 

AX2012: (Inventory management/Setup/Journals/Journal names, inventory/General/Voucher)
D365: (Inventory management/Setup/Journal names/Inventory/General/Voucher)

 

 

 

So, this blog post is about how to extend this functionality by enabling the generation of a new voucher per journal line.

 

Example: We have 10 lines in the inventory movement journal, and we want to generate 10 different vouchers -one voucher per journal line.

 

Step-by-step Instructions for AX2012

 

  • The first step is to extend the enum InventJournalVoucherChange and add a new option ‘Line change’:

 

 

    • After this step, we should be able to select ‘Line change’ as an option in the inventory journal name setup:

 

 

  • Then we should modify the class InventJournalTransData and override the initVoucher method. This method is the place where we can control the voucher number generation for each journal line.

 

 

  • Now, we want to keep the same logic for voucher numbers when the setup is ‘Change date’ or ‘Change date or item’.

 

    • For that purpose, we need to copy the code from the \Classes\JournalTransData\initVoucher and add it in case the setup is ‘Change date’ or ‘Change date or item’. In case the setup is our new option ‘Line change’, then we need to add a code that will generate the new voucher number.

 

    • After these changes the initVoucher method on the InventJournalTransData should look something like below:

 

 

  • Now, when we create an inventory movement journal with 10 lines and post the journal, we will see 10 different vouchers created for each journal line.

 

Step-by-step Instructions for D365

 

  • The first step is to create an extension of the enum InventJournalVoucherChange and add a new option ‘Line change’:

 

    • After this step, we should be able to select ‘Line change’ as an option in the inventory journal name setup:

 

 

  • Then we should create an extension of the class InventJournalTransData and using Chain of command we will add the new initVoucher method.

 

    • Please note that this solution needs to call the super() (next call) and for that reason is going to create a new voucher number in the super call, and then we are going to override that voucher number with a new one.

 

  • Now, when we create an inventory movement journal with 10 lines and post the journal, we will see 10 different vouchers created for each journal line.

‘View Details’ Button Does Not Work on EDT Fields Used in Forms Displaying Records from Customized Tables

In previous versions of Microsoft Dynamics AX, table relationships could be defined in the Application object tree under the Relations node of either an EDT node or a specific table node.

 

Starting with Microsoft Dynamics AX 2012, the use of relations on EDT is obsolete and should be deleted in its future versions.

 

All table relations defined under an EDT should be migrated since they should be defined on tables only. If this is not done correctly, the ‘View details’ option can be unavailable for EDT fields used in forms displaying records from customized tables. This problem might occur even though the ReferenceTable property on the EDT is set to some table and this EDT matches the type of the primary index from the corresponding reference table.

 

Our team came to a different possible solution to this problem, which is explained in the text below:

 

Firstly, you need to create a self-relation in the table whose primary index matches the EDT.

 

view details' button does not work on EDT fields used in forms displaying records from customized tables

 

Another option is to create a hidden menu item button, which opens the main form of the data from the table whose primary index matches the given EDT.

 

Next, on the grid displaying the records from this table set this button as a default action. ‘View button’ option will appear on right-clicking the first field in the grid.

 

It is important to note that this is not the best solution since this works only on forms containing a grid with the corresponding table set as the main data source.

 

If you want to use this field from the same table in other forms, the ‘View details’ button won’t be shown again, unless the previously mentioned conditions are fulfilled. This approach is used in the Sales agreements list page.

 

edt fields

edt fields 2

 

edt fields 3

How to Set Up Team Foundation Server on Visual Studio in Dynamics AX 365?

In this blog post, we will provide you with the required steps to configure your VS to enable source control on your model.

 

Create a New Model and Project

 

The first thing we need to do is to create a new project and a model which will be used for that project. You can find out here how to do that.

Connect to Team Foundation Server

 

The next step is to connect to your Team Foundation Server. To do this, we need to follow up these steps:

 

  • Open the Team Explorer window (View -> Team Explorer)

 

  • Click on the manage connection button and select Connect to Team Project

 

tfs1

 

  • From the newly open window click on Servers… button and add your TFS and connect to it

 

tfs

 

Create the Folder Structure

 

Before starting with development, we need to open the Source Control Explorer and create the following recommended folder structure in our team project.

 

 

tfs2

 

The source XML files organized by packages and models will be held in the Metadata folder and our VS projects will be in the Projects folder.

 

Mapping

 

Next step is to configure the mapping for the Metadata and Projects server folder to a local metadata/project folder that will hold our VS solution and project files.

 

Add the Descriptor

 

The last step required to be done is to add the mapping for the Descriptor or you will face the problem a lot of people encounter during this configuration “The model <ModelName> specified on the project cannot be found.”

 

To do this first create such folders locally inside your model repository and drag/drop the Descriptor and XppMetadata folder from there to the TFS branch in the VS.  This will automatically create the mapping for these two folders.

 

 

You can then start with development in your newly configured environment.

Print Multiple Invoices into One PDF File in AX7

The Problem

 

One of the improvements that we had to do for one of our clients was the possibility to select multiple invoices from the grid control and then print them all into one PDF, instead of the default action of creating a separate PDF file per invoice.

 

print multiple invoices into one pdf

 

In our particular case, the print destination settings are maintained for all the invoices and confirmed on the press of the OK button.

 

print destination settings

The Solution

 

To perform the operation, we had to extend the system SRSReportRunController class and to create an additional method “runFromArgs(Args _args)” where we replicated all the steps that are performed when the report is run, with the purpose of getting the rendered report bytes for the selected invoices.

 

The next step was to put the data into a container and use the merging method where the container is being read as a stream and afterwards written to a file and sent to the user.

 

In this process, we used the PDFsharp library (PDRSharp.dll) which needed to be added as a reference to the project.

 

public static void runFromArgs(Args _args)
{
if (!_args || _args.dataset() != tableNum(InvoiceTable))
{
            throw error(“@SYS22996”);
}

InvoiceTable invoice = _args.record() as InvoiceTable;

Map selectedRecordMap =          Map::create(Helpers::getFormRecordOrderedByField(FormDataUtil::getFormDataSource(invoice), fieldNum(InvoiceTable,InvoiceNum)));

MapEnumerator mapEnum = selectedRecordMap.getEnumerator();

boolean firstReport = true;
boolean printMediumFile;
container con;
str mergedFileName;
boolean proceed = true;
SRSPrintMediumType firstMediumType;

while (mapEnum.moveNext())
{
   PdfSharp.Pdf.PdfDocument initialDocument;
   invoice = mapEnum.currentValue();
           ExtendedInvoiceReportController controller = new ExtendedInvoiceReportController();
           controller.parmReportName(invoice.PSOInvoiceLayoutTable().ReportName);
           _args.record(invoice);
           controller.parmArgs(_args);
   controller.parmShowDialog(firstReport==true);

   SrsReportDataContract reportContract = controller.parmReportContract();
   SRSReportExecutionInfo executionInfo;
   guid reportRunId = newGuid();
   executionInfo = new SRSReportExecutionInfo();
   executionInfo.parmReportRunId(reportRunId);
   reportContract.parmReportExecutionInfo(executionInfo);
           reportContract.parmReportServerConfig(SRSConfiguration::getDefaultServerConfiguration());

   SRSPrintDestinationSettings printSettings;
   printSettings = reportContract.parmPrintSettings();

   if(firstReport)
   {
controller.prompt();
   }
   else
   {
printSettings.printMediumType(firstMediumType);
printSettings.fileFormat(SRSReportFileFormat::PDF);
printSettings.overwriteFile(true);
   }
   if(controller.dialogCanceled && firstReport == true)
   {
proceed = false;
firstReport = false;
   }
   if(proceed)
   {
             if(printSettings.printMediumType() == SRSPrintMediumType::File)
             {
                 firstMediumType = printSettings.printMediumType();
                 controller.runReport();

Microsoft.Dynamics.AX.Framework.Reporting.Shared.ReportingService.ParameterValue[]  parameterValueArray;
Map reportParametersMap;
rsReportRunPrinter reportRunPrinter;

SrsReportRunService service = new SrsReportRunService();
service.getReportDataContract(invoice.InvoiceLayoutTable().ReportName);
service.preRunReport(reportContract);

reportParametersMap = service.createParamMapFromContract(reportContract);

parameterValueArray = SrsReportRunUtil::getParameterValueArray(reportParametersMap);
reportRunPrinter = new SrsReportRunPrinter(reportContract, parameterValueArray);

SRSReportFileFormat fileFormat = printSettings.fileFormat();
System.Byte[] reportBytes;

SRSProxy proxy = reportRunPrinter.parmSrsProxy();
                reportBytes = proxy.renderReportToByteArray(reportContract.parmReportPath(),
                                                  parameterValueArray,
                                                  fileFormat,
                                                  printSettings.deviceInfo());

                str content = System.Convert::ToBase64String(reportBytes);

                if(firstReport)
                {
                           mergedFileName = printSettings.parmFileName();
                }
                con += content;
                firstReport = false;

                printMediumFile = true;

                }
                else
                {
                   irstMediumType = printSettings.printMediumType();
                   controller.runOperation();
                   firstReport = false;
                }
             }
         }
         if(printMediumFile)
        {
           Helper::mergePDFs(con, mergedFileName);
        }

  }

 

The Method

 

Further on, the actual method for merging of the PDFs is as follows:

 

   static PdfSharp.Pdf.PdfDocument mergePDFs(container _con, str _mergedName)
    {
        PdfSharp.Pdf.PdfDocument outPutPDFDocument = new PdfSharp.Pdf.PdfDocument();
        PdfSharp.Pdf.PdfDocument inputPDFDocument = new PdfSharp.Pdf.PdfDocument();
        PdfSharp.Pdf.PdfPages pdfPages;
        container con = _con;
        int i, j, pageCount;
        FileName pdfFile;
        InteropPermission permission;
        str errorMessage;
        System.IO.MemoryStream mergedStream;
        str tempDir;
        str mergedName = ‘INVOICES_’ + _mergedName;

        try
        {
            permission = new InteropPermission(InteropKind::ClrInterop);
            permission.assert();

            for (i = 1; i <= conLen(con); i++)
            {

                System.Byte[] reportBytes;
                System.IO.MemoryStream stream;

                reportBytes = System.Convert::FromBase64String(conPeek(con,i));

                stream  = new System.IO.MemoryStream(reportBytes);
inputPDFDocument = PdfSharp.Pdf.IO.PdfReader::Open(stream,PdfSharp.Pdf.IO.PdfDocumentOpenMode::Import);
                outputPDFDocument.set_Version(inputPDFDocument.get_Version());
                pageCount = inputPDFDocument.get_PageCount();
                pdfPages = inputPDFDocument.get_Pages();

                if (pageCount > 0)
                {
                    for (j = 0 ; j < pageCount; j++)
                    {
                        outputPDFDocument.AddPage(pdfPages.get_Item(j));
                    }
                }
            }
            new InteropPermission(InteropKind::ClrInterop).assert();

    tempDir = ‘C:\\tempDir’;
            System.IO.Directory::CreateDirectory(tempDir);

            CodeAccessPermission::revertAssert();
            outputPDFDocument.Save(tempDir + ‘\\’ + mergedName);
            System.Byte[] outBytes = System.IO.File::ReadAllBytes(tempDir + ‘\\’ + mergedName);
            mergedStream = new System.IO.MemoryStream(outBytes);
            File::SendFileToUser(mergedStream, mergedName);
            CodeAccessPermission::revertAssert();

            new InteropPermission(InteropKind::ClrInterop).assert();
            PSOHelpers::clearDirectory(tempDir);
            System.IO.Directory::Delete(tempDir);
            CodeAccessPermission::revertAssert();
        }
        catch(Exception::CLRError)
        {
            errorMessage = AifUtil::getClrErrorMessage();
            CodeAccessPermission::revertAssert();
            throw error(errorMessage);
        }
        return outputPDFDocument;
    }

    static void clearDirectory(Filename _folderName)
    {
        System.String[] files;
        int             filecount;
        int             i;

        if (WinAPI::folderExists(_folderName))
        {
            files = System.IO.Directory::GetFiles(_folderName);
            filecount = files.get_Length();

            for(i=0; i < filecount; i++)
            {
                System.IO.File::Delete(files.get_Item(i));
            }

        }

   }

 

Side Effects

 

As a side effect, we faced the problem that extra PDF files were being created for all the invoice reports that were additionally going to be sent to the client along with the merged file, which would be pretty annoying for the client.

 

We managed to resolve that problem with the use of a post handler for the “onToFile” method from the SRSPrintDestinationSettingsDelegates class, where the method was set to not return a value in a set condition.

 

[PostHandlerFor(classStr(SRSPrintDestinationSettingsDelegates), methodStr(SRSPrintDestinationSettingsDelegates, onToFile))]
public static void SRSPrintDestinationSettingsDelegates_Post_onToFile(XppPrePostArgs args)
{
        ProjectHoursContract projectHoursContract;
        SrsReportDataContract dataContract;

        dataContract = args.getArgNum(2);
        projectHoursContract = dataContract.parmRdpContract() as ProjectHoursContract;

        if(!projectHoursContract)
        {
            args.setReturnValue(false);
        }
}

Charts in AX7

Charts in AX 7 are new features that gives great visual overview on records and they are great improvement from the old way of creating charts on AX 2012.

 

They are usually used as Parts forms to another forms mostly to Workspaces and they shows calculated information represented as  graphs or charts.

 

For creating Form with chart is used “Hub Part Chart” pattern and this pattern is contained of HeaderGroup (optional) and Chart control.

 

Chart control contains 5 sub-nodes:

 

  • Bindings –used for binding records to specific range or group of records as some names or items or orders.
  • Series – used to specify series of data bind to specified Binding data as dates, calculated sums or quantities.
  • Secondary Y-Axes – used to specify second series that will be bind to Binding data.
  • Methods
  • Events

 

To be able to control Bindings in code you can use it the method from the chart DataSets(), same if we want to change the Series with code we can use method Measures()

 

List mdt = chartControlName.Measures();
ListEnumerator mdit = mdt.getEnumerator();
while (mdit.moveNext())
{
                    SysChartMeasure ms = mdit.current();
                    ms.parmKeyField(fieldStr(dataSourceName,ResourceTeamName));
}
chartControlName.Measures(mdt);
ds.parmCategoryField(fieldStr(dataSourceName, ResourceTeamName));
ds.parmDataSource(tableStr(dataSourceName));
chartControlName.parmCategoriesType(SysChartCategoriesType::String);
chartControlName.DataSets(fd);               
chartControlName.ZoomLimit(SysChartDateTimeInterval::Default);
chartControlName.update();

 

When you change the categories field in code to field that is from another type that defined one you must define the type of the categories.

 

Example we have chart with bindings (categories properties) to field TransDate from type date, and we try to change through code to field Name from type string we must use parmCategoriesType(SysChartCategoriesType::String) method.

 

When we change some parameters in the chart we need to use update() method for this changes to be refreshed to the current form.

 

There is possibility to do some action from menu item when you click on some part of the chart bars which is controlled with properties on the chart MenuItemType and MenuItemName or with override to a method DrillThrough() on the chart.

 

public void DrillThrough(str _contextObject)
{
       Args args = new Args();
       args.parmObject(hourStatisticsHelper.createRangeForOverview());
       args.record(PSOHourStatisticsAggEntity);
       new MenuFunction(menuItemDisplayStr(PSOTSTimesheetLineOverview), MenuItemType::Display).run(args);
} 

Determining the Difference Between Dynamics AX(7) and Dynamics 365

The ERP world is shifting to the cloud causing rifts in this space because of the difference in cloud and on-premise installations; this blog Dynamics AX(7) versus Dynamics 365 Enterprise Edition hopes to explain the new functions, terms, and how Dynamics 365 relates to the new Dynamics AX (7).

 

How to Get Dynamics AX 7?

 

The new release of Dynamics AX (7) can be purchased now for over 20 users in the public cloud; it will be offered on premise sometime in 2017. The New Dynamics AX is a game changer for ERP in that it can be used anytime, anywhere, on any device and it includes development space. A package deal including CRM will soon also be available on Microsoft Azure cloud platform by subscribing to Microsoft Dynamics 365 Enterprise Edition plan 2 (see more details on our site).

 

What is Microsoft Dynamics 365?

 

Microsoft Dynamics 365 is evolving into a total business software package allowing Microsoft’s current CRM and ERP cloud solutions to be deployed in one cloud service with new role based subscriptions with purpose-built apps to help manage specific business functions like field service, customer service, and project service automation. Using the common data model, Dynamics 365 includes analytics from Cortana Intelligence and Power BI, and the productivity power of Office 365.

 

The Dynamics 365 apps are designed so they can be easily and independently deployed. Your business can start with the apps you need, and as your business demands, you can adopt additional capabilities with ease as all the apps work together.

 

Dynamics 365 is available to purchase a subscription on November 1st.

 

Dynamics 365 Enterprise Plan 1 includes CRM and these Apps:

 

  • Optimized for 250+ Employees;
  • Microsoft Dynamics CRM and light ERP;
  • Min 20 seats on select offers;
  • Powerapps that use the Microsoft common data model (see below);
  • Flow (New App – see more below);
  • Sales;
  • Field Service;
  • Customer Service;
  • Project Service Automation.

 

Plan one does not include operations whereas Dynamics 365 plan 2 includes plan 1 and operations or AX(7). AX (7) will be referred to as Operations in the Dynamics 365 Enterprise Edition.

 

Dynamics 365 Plan 2:

 

  • Includes everything in plan 1;
  • Dynamics AX (7) also referred to as the New Dynamics AX;
  • Adobe Marketing Cloud.

 

The Differences Between Dynamics AX7 versus Dynamics 365

 

The difference of Dynamics AX7 versus Dynamics 365 Enterprise Edition is that your company can purchase plans and not just an on premise application. The plans for Dynamics 365 are offered in two ways: plan 1 & plan 2.