Thursday 21 December 2017

AX7 Dimensions

My current requirement is, we are importing transactions from file. In which MainAccount, Dept and other dime values are existing. By using these values I need to create LedgerDimension and DefaultDimension. 
I tried to use “TMSInvoiceApproval.findOrCreateDynamicDimension(DimensionDynamicAccount _account, DimensionDynamicAccount _dimensions)” method. But couldn’t able to pass parameter values in required format.

LedgerDefaultDimensionValueSet generateDefaultDimension(str _costCentre)
{
List dimensionValue = new List(Types::Class);

DimensionNameValueListContract dimensionNameValueListContract = new DimensionNameValueListContract();
DimensionStorageResult dimensionStorageResult;
DimensionAttributeValueContract dimensionAttributeValueContract;

//#costcentre is the macro for the name of CostCentre dimension attribute

dimensionAttributeValueContract = DimensionAttributeValueContract::construct(#costcentre, _costCentre);
dimensionValue.addEnd(dimensionAttributeValueContract);

dimensionNameValueListContract.parmValues(dimensionValue);

dimensionStorageResult = DimensionNameValueListServiceProvider::newForDimensionNameValueListContract(dimensionNameValueListContract).resolve();

return dimensionStorageResult.parmSavedRecId();
}

inventJournalTrans.DefaultDimension = generateDefaultDimension(“8099”);
=================================================================

In Ax 2012 we have class AxdDimensionUtil that is obsolete in Ax 7. The same should be happen with LedgerAccountDimensionResolver class in AX 7.

     DimensionAttributeValueSetStorage dimensionAttributeValueSetStorage;
        DimensionAttribute dimensionAttribute;
        DimensionAttributeValue dimensionAttributeValue;
        DimensionDefault dimensionDefault;
        LedgerDimensionAccount ledgerDimensionAccount;
        DefaultDimensionIntegrationValues   dimensionNames;
        DimensionDisplayValue               dimensionValues;
        DimensionDynamicAccount             dynamicDimension;

        dimensionAttributeValueSetStorage = new DimensionAttributeValueSetStorage();
        
        dimensionNames = DimensionAttribute::find(DimensionAttribute::getWellKnownDimensionAttribute(DimensionAttributeType::MainAccount)).Name;
        dimensionValues = '110110';

        dimensionNames += SystemParameters::getChartOfAccountsDelimiter();
        dimensionValues += SystemParameters::getChartOfAccountsDelimiter();

        dimensionNames += "BusinessUnit";
        dimensionValues += strReplace('001', SystemParameters::getChartOfAccountsDelimiter(), '\\' + SystemParameters::getChartOfAccountsDelimiter());

        dimensionNames += SystemParameters::getChartOfAccountsDelimiter();
        dimensionValues += SystemParameters::getChartOfAccountsDelimiter();

        dimensionNames += "Department";
        dimensionValues += strReplace('022', SystemParameters::getChartOfAccountsDelimiter(), '\\' + SystemParameters::getChartOfAccountsDelimiter());
        LedgerAccountDimensionResolver ledgerAccountDimensionResolver = LedgerAccountDimensionResolver::newResolver(dimensionValues);
        ledgerAccountDimensionResolver.parmDimensionFormat(dimensionNames);
        dynamicDimension = ledgerAccountDimensionResolver.resolve();
===================================================================

Aging Report

Need to add custFactored Status Field  ( which is enum and maintain as range). In Aging Report :

1.     Added in Contract Field  - Enum(SOLD,NOTSOLD,BLANK)  CustFactored .
2.     Add CustFactored field in Temp Table  “CustAgingReportTmp”, CustTmpAccountSum
3.     CustAgingReportDP  modifications
i)                   Process Report

custVendBalanceList = CustVendBalanceList::construct(SysModule::Cust,
                                                                contract.parmDateTransactionDuedate(),
                                                                contract.parmExcludeZeroBalanceCustomer(),
………………………………………………………….
contract.parmFactorStatus()); //Added Line


ii)                insertCustAgingReportTmp()

insert_recordset custAgingReportTmp
           (Balance01, Balance02, Balance03, Balance04, Balance05, Balance06, Balance07,----------------------------
            HeadingAgingBucketDescription06, HeadingAgingBucketDescription07, Balance,CustFactored,SortOrder)//Added Line

                select
                    Balance01, Balance07, Balance06, Balance05, Balance04, Balance03, Balance02,
------------------------------------------
                    headingAgingBucketDescription06, headingAgingBucketDescription07, balance,CustFactored//Added Line

                from tmpAccountSum
                        join SortOrder
                        from custVendTransAging
                            where tmpAccountSum.AccountNum == custVendTransAging.AccountNum;


4.     Need to retrieve value Cust Factored sent from DP class. – CustVendBalanceList and pass to CustBalanceList

i)                   Construct ()
public static CustVendBalanceList construct(SysModule               _sysModule,
                                            DateTransactionDuedate  _dateTransactionDuedate,
                                            ……………………………………………………………………………………………………………………..
                             NoYes                   _excludeNegativeBalance = NoYes::No,
                             CustFactorStatus        _factorStatus   =   CustFactorStatus::Blank

Since only related to Cust Module my modification I passed only for Cust Case
case SysModule::Cust :
            switch(_dateTransactionDuedate)
            {
                case DateTransactionDuedate::DocumentDate :
                        balanceList = new CustBalancelistDocumentDate(  _range,
-------------------------------------------------------------------------------                                                                       
                                                                        _factorStatus);
                        break;

                case DateTransactionDuedate::TransactionDate :
                        balanceList = new CustBalancelistTransactionDate(   _range,
                                                                            ---------
                                                                            _factorStatus);
                        break;

                default :
                        balanceList = new CustBalanceList(  _range,
                                                            -------
                                                            _factorStatus);
                        break;
            }
            break;


ii)                New ()

void new(PositiveDays               _interval,
         ------------------------------------
         CustFactorStatus  _factorStatus  =   CustFactorStatus::Blank)


5.     Need to Catch the value from CustBalanceList
i)                   New()

void new(PositiveDays               _interval,
         ---------------------------------------
         NoYes                      _excludeNegativeBalance = NoYes::No,
         CustFactorStatus           _factorStatus   =   CustFactorStatus::Blank)

super(_interval, _transactionDate, _zeroDate, _payments, _period, _direction, _agingBucket,
        _printReversed, _details, _excludeZeroBalance, _excludeNegativeBalance,_factorStatus);

agingCalculation = CustVendAgingCalculation::construct(   SysModule::Cust,
                                                        -----------------
                                                        inclTransWithNoBillingClass,
                                                        _factorStatus);

ii)                insertIntoTmpAccountSum()

Since it need to insert value , not update in any of bucket…

if (_withAmountCur)
    {
        insert_recordset custTmpAccountSum
            (AccountNum, CurrencyCode, BillingClassification, InvoiceId, TransDate, Txt, Voucher,CustFactored,Balance01, Balance01Cur, Name, GroupId)
                select AccountNum, CurrencyCode, BillingClassification, InvoiceId, TransDate, Txt, Voucher,CustFactored,sum(Amount), sum(AmountCur)
                    from _agingCalculatedTmp
                        group by AccountNum, CurrencyCode, BillingClassification, TransDate, InvoiceId, Voucher,CustFactored,Txt
                join Name, GroupId from customers
                    group by Name, GroupId
                        where _agingCalculatedTmp.AccountNum == customers.AccountNum;




Methods where  actually they include the transactions :
  [s]    \Classes\CustAgingCalculation\selectTransactions 
  \Classes\CustAgingCalculation\selectDetailsOfTransactions



  [s]    \Classes\CustAgingCalculation\selectTransactions                                                       13
 
update_recordSet agingProcessingTmp setting
        IsPayment = true
            where agingProcessingTmp.AmountCur < 0
                exists join custTrans
                    where custTrans.RecId == agingProcessingTmp.TransRecId
                        && !custTrans.Invoice;


[s]    \Classes\CustAgingCalculation\selectDetailsOfTransactions                                               1
case DateTransactionDuedate::DueDate:

            update_recordSet agingProcessingDetailsTmp setting
                TransDate = custSettlement.DueDate
                    join agingProcessingTmp
                        where agingProcessingTmp.RecId == agingProcessingDetailsTmp.ProcessingRecId
                    join custSettlement
                        where agingProcessingTmp.SettlementRecId == custSettlement.RecId
                            && custSettlement.TransDate > balanceAsOfDate
                            && custSettlement.DueDate > dateNull();

            update_recordSet agingProcessingDetailsTmp setting
                TransDate = custTransOpen.DueDate
                    join agingProcessingTmp
                        where agingProcessingTmp.RecId == agingProcessingDetailsTmp.ProcessingRecId
                    join custTransOpen
                        where agingProcessingTmp.TransOpenRecId == custTransOpen.RecId
                            && custTransOpen.TransDate <= balanceAsOfDate
                            && custTransOpen.DueDate > dateNull();
            break;

        case DateTransactionDuedate::DocumentDate:
            update_recordSet agingProcessingDetailsTmp setting
                TransDate = custTrans.DocumentDate
                    join agingProcessingTmp
                        where agingProcessingTmp.RecId == agingProcessingDetailsTmp.ProcessingRecId
                    join custTrans
                        where agingProcessingTmp.TransRecId == custTrans.RecId
                            && custTrans.TransDate <= balanceAsOfDate
                            && (!custTrans.Closed || custTrans.Closed >= balanceAsOfDate)
                            && custTrans.DocumentDate > dateNull()
                    exists join customersVendors

                        where custTrans.AccountNum == customersVendors.AccountNum;


1.)    Remove the date 12/31/2154 – this occurs when the unit setup is ‘0’ and interval ‘unlimited’ in the aging period definition
A.      To navigate this:
AP>> setup>> Aging Period Definitions.




If date is 2154/12/31 - then the value should be null :
---------------------------------------------------------

=iif(Fields!Heading14.Value = FormatDateTime(CDate("2154/12/31"),2), "",Fields!Heading14.Value)

If we want Date  from Date time :
---------------------------------------

Format(Fields!Heading7DateValue.Value, Microsoft.Dynamics.Framework.Reports.BuiltInMethods.GetExtendedDataTypeFormat("Transdate", Parameters!AX_RenderingCulture.Value))


=======================================================

=iif(Fields!Heading14.Value = FormatDateTime(CDate("2154/12/31"),2), "",Format(Fields!Heading14.Value, Microsoft.Dynamics.Framework.Reports.BuiltInMethods.GetExtendedDataTypeFormat("Transdate", Parameters!AX_RenderingCulture.Value)))
=====================================================


Monday 18 December 2017

X++ code to read CSV files in Dynamics 365 for operations

Reading data from csv files is a very common development requirement for X++ developers. In Dynamics 365 for operations there are some interesting concepts, as opposed to AX2012. The application runs on cloud and the client is in web. In such scenarios the way to read files from local system is a 2 step process.
Step 1: Upload the file from local machine to Microsoft cloud (Azure) storage space called as Azure blob.
Step 2: Read the file from Azure blob to retrieve the data.
 The standard API's are used here do the required magic. I have tried to breakdown the functionality of the calls. 
Step 1 is done by the below command where the file is uploaded from local machine to azure blob


 
Step 2 is done by opening the IO stream from the Blob and reading it from the IO class

 

Rest of the job is almost similar to AX2012 pattern.
 
To test the job we create a sample csv file 

 
 
On running the job a file picker will be shown, where you can browse the file.



 
The progress of uploading it to cloud space is shown in the progress bar


 
 
 
 
Infolog is shown with the data on the file 


 
 

Worth mentioning that there are various other IO classes, provided out of the box which have the below associations
 

 
 
The file upload classes have different strategies and the below classes are available out of the box. For more information refer to nice articles mentioned in the references:


To quickly reuse the code here it goes 

class RGReadSample
{        
    /// 
    /// Runs the class with the specified arguments.
    /// 
    /// The specified arguments.
    public static void main(Args _args)
    {        
        AsciiStreamIo                                   file;
        Array                                           fileLines;
        FileUploadTemporaryStorageResult                fileUpload;

        fileUpload = File::GetFileFromUser() as FileUploadTemporaryStorageResult;
        file = AsciiStreamIo::constructForRead(fileUpload.openResult());
        if (file)
        {
            if (file.status())
            {
                throw error("@SYS52680");
            }

            file.inFieldDelimiter(',');
            file.inRecordDelimiter('\r\n');
        }

        container record;
        while (!file.status())
        {
            record = file.read();

            if (conLen(record))
            {
                info(strFmt("%1 - %2",conPeek(record,1),conPeek(record,2)));
            }
        }

        info("done");
    }

}