Tuesday, 4 September 2018

Custom SSRS report in the Print management section in Ax 2012 using X++

Requirement: 
To add the new custom SSRS report (in my case the report is calling the print management settings from SL, PL, customer and vendor) in the Print management section which can be access from Sales Ledger, Customer, Purchase Ledger, Vendors.

Path of Print management:
Sales Ledger: SalesLedger/Setup/Forms/Form setup/button: Print management
Purchase Ledger: Purchase ledger/Setup/Forms/Form setup/button: Print management
Customer: Sales ledger/Common/Customers/All customers/General tab/Set up group/ Print management
Vendor: Purchase ledger/Common/Vendors/All vendors/General tab/Set up group/ Print management.


Code changes:


Enum:
Add report name in the below Enums which you want to show on Print management setUp:
\Data Dictionary\Base Enums\PrintMgmtNodeType
\Data Dictionary\Base Enums\PrintMgmtDocumentType

Tables:

Add the following conditions at the bottom as shown below in the populate method of table PrintMgmtReportFormat
\Data Dictionary\Tables\PrintMgmtReportFormat\Methods\populate: 

addAX (PrintMgmtDocumentType::SalesAdvancePayment); 
addAX (PrintMgmtDocumentType::PurchAdvancePayment);
addAX (PrintMgmtDocumentType::LoadAdviceNote);
Here SalesAdvancePayment, PurchAdvancePayment and LoadAdviceNote are my SSRS report representatives on the print management setUp:
Here is an example:

Classes: Add the following changes in the below classes at the end of the methods.
PrintMgmtHierarchy_Purch:
Method: getNodesImplementation(): 
supportedNodes.addEnd(PrintMgmtNodeType::PurchAdvancePayme
supportedNodes.addEnd(PrintMgmtNodeType::LoadAdviceNote);
Method: getParentImplementation():
case PrintMgmtNodeType::PurchAdvancePayment:
// When no parent is present, return null
result.parmReferencedTableBuffer(null);
result.parmNodeDefinition(PrintMgmtNode::construct(PrintMgmtNodeType::PurchAdvancePayment));
break;

case PrintMgmtNodeType::LoadAdviceNote:
// When no parent is present, return null
result.parmReferencedTableBuffer(null);
result.parmNodeDefinition(PrintMgmtNode::construct(PrintMgmtNodeType::LoadAdviceNote));
break;

Similarly for Class: PrintMgmtHierarchy_Sales:
Methods:
\Classes\PrintMgmtHierarchy_Sales\getNodesImplementation
\Classes\PrintMgmtHierarchy_Sales\getParentImplementation

\Classes\PrintMgmtNode_CustTable: 
Methods: getDocumentTypes()
if (isConfigurationkeyEnabled(configurationKeyNum(LedgerBasic)))
{
docTypes.addEnd(PrintMgmtDocumentType::SalesAdvancePayment);
}
Similarly for these classes also:
\Classes\PrintMgmtNode_VendTable
if (isConfigurationkeyEnabled(configurationKeyNum(LedgerBasic)))
{
docTypes.addEnd(PrintMgmtDocumentType::PurchAdvancePayment);
docTypes.addEnd(PrintMgmtDocumentType::LoadAdviceNote);
}
\Classes\PrintMgmtNode_Purch:
Method: getDocumentTypes()
if (isConfigurationkeyEnabled(configurationKeyNum(LedgerBasic)))
{
docTypes.addEnd(PrintMgmtDocumentType::PurchAdvancePayment);
docTypes.addEnd(PrintMgmtDocumentType::LoadAdviceNote);
}
\Classes\PrintMgmtNode_Sales:
Method: getDocumentTypes()
if (isConfigurationkeyEnabled(configurationKeyNum(LedgerBasic)))
{
docTypes.addEnd(PrintMgmtDocumentType::SalesAdvancePayment);
}
\Classes\PrintMgmtNode:
Method: construct()
case PrintMgmtNodeType::SalesAdvancePayment:
return new PrintMgmtNode_Sales();
case PrintMgmtNodeType::PurchAdvancePayment:
return new PrintMgmtNode_Purch();
case PrintMgmtNodeType::LoadAdviceNote:
return new PrintMgmtNode_Purch();

Class: PrintMgmtDocType
\Classes\PrintMgmtDocType\getDefaultReportFormat
case PrintMgmtDocumentType::SalesAdvancePayment:
return ssrsReportStr(AdvSettlPayment, Report);
case PrintMgmtDocumentType::PurchAdvancePayment:
return ssrsReportStr(AdvSettlPayment, Report);
case PrintMgmtDocumentType::LoadAdviceNote:
return ssrsReportStr(AdvSettlPayment, Report);

\Classes\PrintMgmtDocType:
Methods: 
getDefaultReportFormat():
case PrintMgmtDocumentType::SalesAdvancePayment:
            return ssrsReportStr(AdvSettlPayment, Report);

        case PrintMgmtDocumentType::PurchAdvancePayment:
            return ssrsReportStr(AdvSettlPayment, Report);

        case PrintMgmtDocumentType::LoadAdviceNote:
            return ssrsReportStr(AdvSettlPayment, Report);

getQueryTableId():
case PrintMgmtDocumentType::SalesAdvancePayment:
tableId = tableNum(SalesTable);
break;
case PrintMgmtDocumentType::PurchAdvancePayment:
tableId = tableNum(PurchTable);
break;
case PrintMgmtDocumentType::LoadAdviceNote:
tableId = tableNum(CTMContractLoads);
break;

getQueryRangeFields()
case PrintMgmtDocumentType::SalesAdvancePayment:
fields.addEnd(fieldNum(SalesTable, InvoiceAccount));
fields.addEnd(fieldNum(SalesTable, SalesId));
break;
case PrintMgmtDocumentType::PurchAdvancePayment:
fields.addEnd(fieldNum(PurchTable, InvoiceAccount));
fields.addEnd(fieldNum(PurchTable, PurchId));
break;
case PrintMgmtDocumentType::LoadAdviceNote:
fields.addEnd(fieldNum(CTMContractLoads, LoadId));
break;
About controller class for your report:
Now extend your controller class with "SrsPrintMgmtController"

After all of the above set Up I am facing an issue that:
If I open the print management settings from “SalesLedger/Setup/Forms/Form setup/button: Print management” 
And do the setting here it’s working fine, my report is picking the print destination setting correctly from here.
But if I delete the settings from the above path and do the same setting from the other path which is “Sales ledger/Common/Customers/All customers/General tab/ SetUp/Print management button”
The report is not considering the print management settings here and running on screen only.

Solution for this will be:
You have to find the PrintMgmtSettings table based on its relation with the PrintMgmtDocInstance for the customer or vendor and then pass it's "PrintJobSettings" field value which is a container to the "SRSPrintDestinationSettings"
class.
You can find this code in the "runReport" method of my controller class below:

Controller class:
public class AdvSettlPaymentPrintController extends SrsPrintMgmtController
{
    int                         numOfOrders;
    AdvSettlPaymentContract     advSettlPaymentContract;
    CTMPaymentType              ctmpaymentType;
    List                        ordersToProcessList;
    UsePrintMgmt                advSettlPaymentUsePrintMgmt;
    SRSPrintDestinationSettings destnationSettings;

    #define.PurchAdvance('PurchAdvance')
    #define.PurchSettlement('PurchSettlement')
    #define.SalesAdvance('SalesAdvance')
    #define.SalesSettlement('SalesSettlement')
    #define.LoadAdviceNote('LoadAdviceNote')
}
public void getFromDialog()
{
    SrsReportDataContract   dataContract = this.parmReportContract();

    destnationSettings = dataContract.parmPrintSettings();

    super();
}

//This method will gives you the power if you don't want to run the print management settings and run normal report (Added a new Check box on the dialog "Use print management destination")


public UsePrintMgmt parmUsePrintMgmt(UsePrintMgmt _advSettlPaymentUsePrintMgmt = advSettlPaymentUsePrintMgmt)
{
    AdvSettlPaymentContract     advSettlPaymentContractLocal;
    SrsReportDataContract       dataContract = this.parmReportContract();

    advSettlPaymentContractLocal = dataContract.parmRdpContract();

    if (_advSettlPaymentUsePrintMgmt == advSettlPaymentContractLocal.parmAdvSettlPaymentUsePrintMgmt())
    {
        advSettlPaymentUsePrintMgmt = _advSettlPaymentUsePrintMgmt;
    }
//If the check box on the dialog is off then run "current print destination"
    else
    {
        advSettlPaymentUsePrintMgmt = advSettlPaymentContractLocal.parmAdvSettlPaymentUsePrintMgmt();
    }

    return advSettlPaymentUsePrintMgmt;
}

protected void preRunModifyContract()
{
    SrsReportDataContract           dataContract = this.parmReportContract();

    advSettlPaymentContract = dataContract.parmRdpContract();
    advSettlPaymentContract.callerPaymentType(this.parmPaymentType());
    advSettlPaymentContract.parmListofOrders(this.listOfOrders());
    advSettlPaymentContract.parmNumOfRecToProcess(this.parmNumofOrders());

    super();
}
//Construct the printMgmtReportRun class in the runPrintMgmt method based on the conditions.
protected void runPrintMgmt()
{
    if (ctmpaymentType == CTMPaymentType::SalesAdvance)
    {
         printMgmtReportRun = PrintMgmtReportRun::construct(PrintMgmtHierarchyType::Sales, PrintMgmtNodeType::SalesAdvancePayment, PrintMgmtDocumentType::SalesAdvancePayment);
    }
    else if (ctmpaymentType == CTMPaymentType::SalesSettlement)
    {
        printMgmtReportRun = PrintMgmtReportRun::construct(PrintMgmtHierarchyType::Sales, PrintMgmtNodeType::SalesAdvancePayment, PrintMgmtDocumentType::SalesAdvancePayment);
    }
    else if (ctmpaymentType == CTMPaymentType::PurchAdvance)
    {
        printMgmtReportRun = PrintMgmtReportRun::construct(PrintMgmtHierarchyType::Purch, PrintMgmtNodeType::PurchAdvancePayment, PrintMgmtDocumentType::PurchAdvancePayment);
    }
    else if (ctmpaymentType == CTMPaymentType::PurchSettlement)
    {
        printMgmtReportRun = PrintMgmtReportRun::construct(PrintMgmtHierarchyType::Purch, PrintMgmtNodeType::PurchAdvancePayment, PrintMgmtDocumentType::PurchAdvancePayment);
    }
    else if (ctmpaymentType == CTMPaymentType::LoadAdviceNote)
    {
        printMgmtReportRun = PrintMgmtReportRun::construct(PrintMgmtHierarchyType::Purch, PrintMgmtNodeType::LoadAdviceNote, PrintMgmtDocumentType::LoadAdviceNote);
    }
    printMgmtReportRun.parmReportRunController(this);

    printMgmtReportRun.load(this.parmArgs().record(), this.parmArgs().record(), Global::currentUserLanguage());

    this.outputReports();
}

//This method used to find the PrintMgmtSettings table value and pass it explicitely into SRSPrintDestinationSettings class if you need to pass the print management settings for a customer or vendor.
//I have added conditions like 
if Print management settings present on the Sales or Purchase Ledger, then use them, otherwise check for the print management settings for a customer or vendor and pass them.
public void runReport()
{
    SrsReportDataContract       dataContract = this.parmReportContract();
    SRSPrintDestinationSettings printDestinationSettings;
    PrintMgmtReportFormat       printMgmtReportFormat;
    PrintMgmtDocInstance        printMgmtDocInstanceCust;
    PrintMgmtDocInstance        printMgmtDocInstanceSales;
    PrintMgmtDocInstance        printMgmtDocInstancePurch;
    PrintMgmtDocInstance        printMgmtDocInstanceVend;
    PrintMgmtSettings           printMgmtSettings;
    PurchTable                  purchTable;
    SalesTable                  salesTable;
    CustTable                   custTable;
    VendTable                   vendTable;

    if (this.parmArgs().dataset() == tableNum(PurchTable))
    {
        purchTable = this.parmArgs().record();

        select firstOnly printMgmtDocInstancePurch
            where printMgmtDocInstancePurch.DocumentType  == PrintMgmtDocumentType::PurchAdvancePayment
            &&    printMgmtDocInstancePurch.NodeType      == PrintMgmtNodeType::Purch
            &&    printMgmtSettings.ParentId              == printMgmtDocInstancePurch.RecId
            &&    printMgmtSettings.Description           != "";

        if (!printMgmtDocInstancePurch.RecId)
        {
            // For Supplier print management settings
            printMgmtSettings.clear();

            select firstOnly DocumentType, ReferencedRecId, ReferencedTableId, RecId from printMgmtDocInstanceVend
                join  RecId, AccountNum from vendTable
                join PrintJobSettings from PrintMgmtSettings
                    where printMgmtDocInstanceVend.DocumentType         == PrintMgmtDocumentType::PurchAdvancePayment
                &&        printMgmtDocInstanceVend.ReferencedRecId      == vendTable.RecId
                &&        printMgmtDocInstanceVend.ReferencedTableId    == tableNum(vendTable)
                &&        vendTable.AccountNum                          == purchTable.OrderAccount
                &&        printMgmtSettings.ParentId                    == printMgmtDocInstanceVend.RecId
                &&        printMgmtSettings.Description                 != "";
        }
    }
    else if (this.parmArgs().dataset() == tableNum(salesTable))
    {
        salesTable = this.parmArgs().record();

        select firstOnly printMgmtDocInstanceSales
            join PrintJobSettings from PrintMgmtSettings
            where printMgmtDocInstanceSales.DocumentType  == PrintMgmtDocumentType::SalesAdvancePayment
            &&    printMgmtDocInstanceSales.NodeType      == PrintMgmtNodeType::Sales
            &&    printMgmtSettings.ParentId              == printMgmtDocInstanceSales.RecId
            &&    printMgmtSettings.Description           != "";

        if (!printMgmtDocInstanceSales.RecId)
        {
            printMgmtSettings.clear();
            // For customer print management settings
            select firstOnly DocumentType, ReferencedRecId, ReferencedTableId, RecId from printMgmtDocInstanceCust
                join  RecId, AccountNum from custTable
                join PrintJobSettings from PrintMgmtSettings
                    where printMgmtDocInstanceCust.DocumentType         == PrintMgmtDocumentType::SalesAdvancePayment
                &&        printMgmtDocInstanceCust.ReferencedRecId      == custTable.RecId
                &&        printMgmtDocInstanceCust.ReferencedTableId    == tableNum(custTable)
                &&        custTable.AccountNum                          == salesTable.CustAccount
                &&        printMgmtSettings.ParentId                    == printMgmtDocInstanceCust.RecId
                &&        printMgmtSettings.Description                 != "";
        }
    }
    if (PrintMgmtSettings.PrintJobSettings)
    {
        printDestinationSettings = new SRSPrintDestinationSettings(printMgmtSettings.PrintJobSettings);
        printDestinationSettings.overwriteFile(true);
        dataContract.parmPrintSettings(printDestinationSettings);
    }

    if (!advSettlPaymentUsePrintMgmt)
    {
        dataContract.parmPrintSettings(destnationSettings);
    }
    super();
}
Finally this is how it looks :
For Customer:

I hope this will be useful for someone.

Best Regards
Pranav

Tuesday, 28 August 2018


Production process summary in Ax (List of classes and tables used in the Production module):
Here is a short summary of various production related terms:
Production module is use for:
Setting up materials (product structure) and resources (operations) required for creating a product
Accounting product production process (accounting materials and resources required for producing a product)
Product is an Item of the BOM item type.
Product is associated with a BOM and a Route.
Bills of Material – BOMS
A Bill of Material or a BOM defines the recipe for making a product. Think of it as a list of ingredients which includes all the sub-assemblies, components, and raw materials required for making a single finished product.
Routes and Operations
A production route defines the process which is used for making a finished product. Sequential steps, or operations, are created which describe the Work Centres involved in the production process and how much time it takes to carry out each operation.
Work Centres
Work Centres represent production resources available to a company for making products. For ex: A Work Centre can be a Machine, a Tool, a Human Operator, or a Vendor.
A Production order is used for managing the product creation process.


Related tables and classes for various Production process:
1) Production Order:
ProdTable: The ProdTable table contains the header information about all of the production orders.
InventTable: Used to initialize the ProdTable for Released product information
InventDim: Used to populate the inventory dimensions.
2) Report as Finished Journal:
Tables:
ProdTable: Contains the production order information.
ProdParmReportFinished:  The ProdParmReportFinished contains parameter that are used when reporting a production order as finished.
ProdJournalProd: ProdJournalProd contains journal lines for report as finished journal.
ProdParametersDim: ProdParametersDim contains site specific production parameters set.
ProdParametersDim: ProdParametersDim contains site specific production parameters set.
ProdJournalName: ProdJournalName contains the journal name entries.
ProdjournalTable: ProdjournalTable contains production journal header information.
InventDim: Used to populate the inventory dimensions.
Classes:
ProdMultiReportFinished: Used to validate and post the Report as Finished Journal as well as validate and post picking list and route card journal.
ProdJournalCheckPostProd: Used to validate and post the Report as Finished Journal.

3) JobCard Journal:
Tables:
ProdParametersDim: ProdParametersDim contains site specific production parameters set.
ProdJournalName: ProdJournalName contains the journal name entries.
ProdjournalTable: ProdjournalTable contains production journal header information.
ProdRoute: ProdRoute contains production route information.
ProdRouteJob: ProdRouteJob contains all of the jobs that were scheduled for production orders.
ProdJournalRoute: ProdJournalRoute contains the route card production journal lines.
Classes:
ProdJournalCheckPostRouteJob: Used to validate and post the Job Card Journal.

4) RouteCard Journal:
Tables:
ProdParametersDim: ProdParametersDim contains site specific production parameters set.
ProdJournalName: ProdJournalName contains the journal name entries.
ProdjournalTable: ProdjournalTable contains production journal header information.
ProdRoute: ProdRoute contains production route information.
ProdJournalRoute: ProdJournalRoute contains the route card production journal lines.

Classes:
ProdJournalCheckPostRoute: Used to validate and post the Route Card Journal.

5) PickingList Journal:
Tables:
ProdParametersDim: ProdParametersDim contains site specific production parameters set.
ProdJournalName: ProdJournalName contains the journal name entries.
ProdjournalTable: ProdjournalTable contains production journal header information.
ProdJournalBOM: ProdJournalBOM contains production journal lines for a picking list.
Classes:
ProdJournalCheckPostBOM: Used to validate and post the picking list journal.


Tuesday, 7 February 2017

How to convert formatted string to num in Dynamics 365

Hi All,

Recently I came with a requirement where I need to compute a string of multiple calculation as shown in an example below:

int a = 100;
int b = 10;
int c = 5;
int d = 10;
Str s = "(a+b)/c*d"
need to get the calculated value for 's'.
str s could contain 'n' number of operation.
If it's Ax 2012 then we can use "formattedstr2num()" or "evalBuf()" but as these are deprecated from Dynamics 365, so here's an exact replacement for these inbuild function:
"ProdMathEvaluator::evaluate()"
Example:
Try to run the below code in job, it will help you to understand the code:
str expr = "(((1+2)*6)/5)";
info(num2str(ProdMathEvaluator::evaluate(expr),0,2,1,3));
Note: If you get the below error:
"Misuse of "Our expression", Please review the elements and symbols used in the equation."

This error comes with "ProdMathEvaluator::evaluate()" method, when our equation contains ',' symbol which majorly happens if value is greater than 999 like 1,000.0.
For this try to use strRem() to resolve the issue as follows:

ProdMathEvaluator::evaluate(strRem(expr, ','));



Best Regards
Pranav Gupta

Insert/Update or remove the default dimension value in D365 FSCM via x++

Use below method to insert/update the dimension values, just pass the parameter values to the method and it will return the updated value: p...