Friday, June 22, 2018

Create and Post Project Expense journal in Dynamics 365 finance and operation

class Job_CreatePostProjExpJournalLines
{      
    /// <summary>
    /// Runs the class with the specified arguments.
    /// </summary>
    /// <param name = "_args">The specified arguments.</param>
    public static void main(Args _args)
    { 
        LedgerJournalName ledgerJournalName;
        LedgerJournalTable ledgerJournalTable;
        LedgerJournalTrans ledgerJournalTrans;
        LedgerJournalTrans_Project ledgerJournalTrans_Project;
        NumberSequenceTable numberSequenceTable;
        ledgerJournalCheckPost ledgerJournalCheckPost;
        ProjLinePropertySetup projLinePropertySetup;
        DirPersonUser personUser;
        HcmWorker worker;
        boolean priceFound = false;
        CostPrice costPriceMST;
        ProjPostCostJournal projPostCostJournal;
        ResourceView resourceView;
        NumberSeq numberseq;
        Amount amount = 753.00;
        boolean result = false;
        TransDate invoiceDate = mkdate(22,06,2018);
        ttsbegin;
        ledgerJournalName = LedgerJournalName::find("PrjJrn");
        if(ledgerJournalName)
        {
            ledgerJournalTable.JournalName = ledgerJournalName.JournalName;
            ledgerJournalTable.initFromLedgerJournalName();
            ledgerJournalTable.JournalNum = JournalTableData::newTable(ledgerJournalTable).nextJournalId();
            ledgerJournalTable.Name = ledgerJournalName.Name + "Sangeet";
            ledgerJournalTable.JournalType = LedgerJournalType::Cost;
            ledgerJournalTable.insert();
            if(ledgerJournalTable)
            {
                select NumberSequence from numberSequenceTable
                    where NumberSequenceTable.RecId == ledgerJournalName.NumberSequenceTable;
                numberseq = NumberSeq::NewGetVoucherFromCode(numberSequenceTable.NumberSequence);
                ledgerJournalTrans.initValue();
                ledgerJournalTrans.JournalNum = ledgerJournalTable.JournalNum;
                ledgerJournalTrans.Voucher = numberseq.voucher();
                ledgerJournalTrans.TransDate = mkdate(22,06,2018);
                ledgerJournalTrans.Qty = 1;
                ledgerJournalTrans.AccountType = LedgerJournalACType::Project;
                ledgerJournalTrans.OffsetAccountType = LedgerJournalACType::Vend;
                ledgerJournalTrans.parmOffsetAccount("1011",ledgerJournalTrans.OffsetAccountType);
                ledgerJournalTrans.TransactionType = LedgerTransType::Payment;
                ledgerJournalTrans.CurrencyCode = "USD";
                ledgerJournalTrans.Invoice = "INV_0011";
                ledgerJournalTrans.Txt = strFmt("Expense Journal Auto Create");
                ledgerJournalTrans.Approved = NoYes::Yes;
                if(amount> 0)
                {
                    ledgerJournalTrans.AmountCurDebit = amount;
                }
                else
                {
                    ledgerJournalTrans.AmountCurCredit = abs(amount);
                }
                ledgerJournalTrans.Company = strUpr(curExt());
                ledgerJournalTrans.OffsetCompany = strUpr(curExt());
                select firstonly personUser where personUser.User == curUserId()
                    join worker
                    where worker.Person == personUser.PersonParty;
                ledgerJournalTrans.Approver = worker.RecId;
                ledgerJournalTrans.PostingProfile = 'GEN';
                ledgerJournalTrans.parmAccount("000004",ledgerJournalTrans.AccountType);
                ledgerJournalTrans.insert();
                if(ledgerJournalTrans)
                {
                    ledgerJournalTrans_Project.initValue();
                    ledgerJournalTrans_Project.CategoryId = "Cat1";
                    ledgerJournalTrans_Project.Qty = 1;
                    ledgerJournalTrans_Project.SalesCurrencyId = ledgerJournalTrans.CurrencyCode;
                    ledgerJournalTrans_Project.LinePropertyId = "Billable";
                    ledgerJournalTrans_Project.ProjTransDate = invoiceDate;
                    select resourceView
                        where resourceView.ResourceCompanyId == curExt();
                    ledgerJournalTrans_Project.Resource = resourceView.RecId;
                    ledgerJournalTrans_Project.ProjId = "000004";
                    ledgerJournalTrans_Project.RefRecId = ledgerJournalTrans.RecId;
                    ledgerJournalTrans_Project.Worker = ledgerJournalTrans.Approver;
                    ledgerJournalTrans_Project.CostPrice = amount;
                    costPriceMST = ProjCostSalesPrice::costPrice(ledgerJournalTrans, ledgerJournalTrans_Project, false);
                    [ledgerJournalTrans_Project.SalesPrice, priceFound] = ProjCostSalesPrice::findCostSalesPrice(ledgerJournalTrans_Project.ProjId,
                        ledgerJournalTrans_Project.Resource,
                        ledgerJournalTrans_Project.CategoryId,
                        costPriceMST,
                        CompanyInfoHelper::standardCurrency(),
                        ledgerJournalTrans_Project.SalesCurrencyId,
                        ledgerJournalTrans_Project.ProjTransDate,
                        ledgerJournalTrans_Project.ProjPriceGroupID,
                        false);
                    ledgerJournalTrans_Project.insert();
                    result = true;
                }
            }
        }
        ttscommit;
        if(result == true)
        {
            ledgerJournalCheckPost = ledgerJournalCheckPost::newLedgerJournalTable(ledgerJournalTable,NoYes::Yes);
            ledgerJournalCheckPost.run();
            info(Strfmt("Project Expense journal posted successfully %1 in %2", ledgerJournalTable.JournalNum,curext()));
        }
    }


}

Friday, June 15, 2018

Error: The text for the following task was not found: 0x0000 , 0x0135 , This could be due to an error or an outdated version of the kernel text file.


Issue: AX 2012 R2 and AX 2012 R3 installed in same machine, you may encountered this error while opening AX application

Error:

The text for the following task was not found:
0x0000 , 0x0135
This could be due to an error or an outdated version of the kernel text file.

Analysis: This error will occurs only if the default path (C:\ProgramFiles(x86)\Microsoft Dynamics AX\60\Client\Bin) you have an AX 2012 R2 client installed. Then the AX 2012 R3 client might use the R2 ktd-files.

Resolution:
  • Navigate to C:\ProgramFiles(x86)\Microsoft Dynamics AX\60\Client\Bin\AxSysEn-US.ktd in a text editor - it will be the R2 ktd-file. (Note: Default language file)
  • Find ">MSGID_TASKTEXTS5" 
  • After "0x1231 DropDialog" add this below following line and save the changes
  • New Line: 0x0135 Microsoft Dynamics Lifecycle Services | Microsoft Dynamics Lifecycle Services
File:

Thursday, June 14, 2018

Filter Default Dimension values through Query in AX 2012/D365

//Method
public void filterQueryByDimension(Query  _query,DataSourceName  _dataSourceName,FieldName   _fieldName)
{
    DimensionProvider   dimensionProvider = new DimensionProvider();
   
    void addDimFilter(Notes _rangeValue,Name  _dimensionName)
    {
        if (_rangeValue)
        {
            dimensionProvider.addAttributeRangeToQuery( _query,
                                                        _dataSourceName,
                                                        _fieldName,
                                                        DimensionComponent::DimensionAttribute,
                                                        _rangeValue,
                                                        _dimensionName);
        }
    }
    dimensionProvider.clearDimensionRangesFromQuery(_query);
       
    addDimFilter("<Dimension values from Contract Paramter>", "BusinessUnit");
    addDimFilter("<Dimension values from Contract Paramter>", "CostCenter");
    addDimFilter("<Dimension values from Contract Paramter>", "Department"); 
}

//Job (tested)
static void SAN_FilterDimensionTestJob(Args _args)
{
    Query                   query = new Query();
    QueryBuildDataSource    qbds;
    QueryRun                qr;
    SalesTable              salesTable;
   
    //Functions
    void addDimFilter(
                        DimensionProvider       _dimensionProvider,
                        Notes                   _rangeValue,
                        Name                    _dimensionName,
                        Query                   _query,
                        DataSourceName          _dataSourceName,
                        FieldName               _fieldName)
    {
        if (_rangeValue)
        {
            _dimensionProvider.addAttributeRangeToQuery( _query,
                                                        _dataSourceName,
                                                        _fieldName,
                                                        DimensionComponent::DimensionAttribute,
                                                        _rangeValue,
                                                        _dimensionName);
        }
    }   
    void filterQueryByDimension(
                                Query           _query,
                                DataSourceName  _dataSourceName,
                                FieldName       _fieldName)
    {
        DimensionProvider   dimensionProvider = new DimensionProvider();
       
        ;
        dimensionProvider.clearDimensionRangesFromQuery(_query);
       
        //addDimFilter(dimensionProvider,"<Dimension values from Contract Paramter>", "BusinessUnit",_query,_dataSourceName,_fieldName);
        //addDimFilter(dimensionProvider,"<Dimension values from Contract Paramter>", "CostCenter",_query,_dataSourceName,_fieldName);
        //addDimFilter(dimensionProvider,"<Dimension values from Contract Paramter>", "Department",_query,_dataSourceName,_fieldName); 
    }
    ;
    qbds = query.addDataSource(tableNum(SalesTable));
   
    filterQueryByDimension(query,qbds.name(),fieldStr(SalesTable, DefaultDimension));
   
    qr = new QueryRun(query);
   
    while (qr.next())
    {
        salesTable = qr.get(tableNum(SalesTable));
       
        info(salesTable.SalesId);
    }
}

Friday, June 8, 2018

Updating Hour journal Indirect Sales Price in to Project funding limit spent in AX 2012


Issue:
System is not updating Hour journal IndirectCost component Sales Price in to Project funding limit spent in AX 2012

Code Fix: (write logic to update Project funding limit Spent)_
AOTàClassà
PsaProjPostEmplIndirectJournalàMethod(ClasscreateAndUpdateTrans)

IDE MORPHX - Access only on Code Read and Debug in AX 2012

Access Restriction on Morphx IDE Development Environment in AX 2012

There is one option to remove the X++ development license from the environment. But doing that means that nobody will be able to even view the code

In this Development, User can able to only Read and Debug the code, will not able to do Create/Update/Delete objects. Even if user having system administrator rights.

Development:
Steps:
·         Add New Enum element --> SysVersionControlType
·         Create New Class --> SANRestrictAXDevAccess extends SysVersionControlSystem
o   Construct Method for new class
o   Override methods
      (Note: Copy below method from class "SysVersionControlSystem" and return code as Below)
Code: return false;
§  allowDelete
§  allowCreate
§  allowEdit
§  allowMoveToModel
§  allowRename
§  isTmpItemOpenedForDelete
                        Code: return true;
§  applyGlobalSettings
§  ideIntegration   
·         Go to Class--> SysVersionControlSystem --> Method (NewType)
Add your new class (SANRestrictAXDevAccess), construct method in Switch case

Setup Changes Requires:
·         AX--> Development Workspace-->Version Control--> Version control Parameters
·         Select new created enum element from version control type list
·         And Version Control status should be enable

Error on Data Entity General Journals Import in Dynamics 365 for finance and operations

Issue/Cause:

1.      In Data Entities, System is inserting GL journal lines without creating reference in header line. In import document (excel or CSV) input for journal name is invalid with master Journal Names setup. 

Resolution: (Code Fix) .BUG
Adding separate new validation method to validate journal Names (validateJournalName).

In default Method (copyCustomStagingToTarget) in LedgerJournalEntity, adding newly added validation method in to it.

Note: 
Overlay requires (Not recommended)
Contact MS support or else wait for until MS releasing this code bug fix

Code: 
//Add new method in LedgerJournalEntity
//Added by Sangeeth - start
private static void validateJournalName(DMFDefinitionGroupExecution _dmfDefinitionGroupExecution, DMFStagingValidationLog _log)
    {
        // Put any rows that are not in Ledger journal name into error state
        LedgerJournalName ledgerJournalName;
        LedgerJournalEntityStaging staging;

        LedgerJournalEntityLog entityLog;
       
        ErrorMsg error = "Journal Name is invalid";

        insert_recordset entityLog (LedgerJournalEntityRecId, ErrorMessage)
            select RecId, error from staging
            where staging.DefinitionGroup == _dmfDefinitionGroupExecution.DefinitionGroup
               && staging.ExecutionId == _dmfDefinitionGroupExecution.ExecutionId
               && staging.TransferStatus == DMFTransferStatus::NotStarted
            exists join ledgerJournalName
                where staging.JournalName != ledgerJournalName.JournalName;


        // write errors to DMF validation log
        update_recordset staging
            setting TransferStatus = DMFTransferStatus::Error
        exists join entityLog
            where staging.RecId == entityLog.LedgerJournalEntityRecId;

        FieldName fieldName = fieldStr(LedgerJournalEntityStaging, JournalName);
        TableName tableName = tableStr(LedgerJournalEntityStaging);
        TableName entityName = "@GeneralLedger:LedgerJournalEntityLabel";
        DMFSourceTarget sourceTarget = DMFSourceTarget::Target;

        insert_recordset _log (DefinitionGroupName, ExecutionId, StagingRecId, StagingTableName, StagingColumnName, SourceTarget, EntityName, ErrorMessage)
            select DefinitionGroup, ExecutionId, RecId, tableName, fieldName, sourceTarget, entityName from staging
        join ErrorMessage from entityLog
            where staging.RecId == entityLog.LedgerJournalEntityRecId;
    }

//Added by Sangeeth - End

//Add below code to validate in
method: copyCustomStagingToTarget

  
//Old code
LedgerJournalEntity::validateJournalType(_dmfDefinitionGroupExecution, log);
LedgerJournalEntity::validateAndUpdateExchangeRates(_dmfDefinitionGroupExecution, log);
LedgerJournalEntity::validateAccountTypes(_dmfDefinitionGroupExecution, log);

  
//Replace by
LedgerJournalEntity::validateJournalName(_dmfDefinitionGroupExecution, log);
LedgerJournalEntity::validateJournalType(_dmfDefinitionGroupExecution, log);
LedgerJournalEntity::validateAndUpdateExchangeRates(_dmfDefinitionGroupExecution, log);

LedgerJournalEntity::validateAccountTypes(_dmfDefinitionGroupExecution, log); 

Wednesday, May 30, 2018

Write Event log entry for AOS runtime error in AX

public server static void writeEventLogEntry(str _message)
{
    System.Diagnostics.EventLog ev;
    System.Boolean sourceExists;
    System.Diagnostics.EventLogEntryType entryType;
    System.String msg;
    ;

    new InteropPermission(InteropKind::ClrInterop).assert();
    sourceExists = System.Diagnostics.EventLog::SourceExists('<AOS Runtime>', '<Computer Name>');
    if (sourceExists)
    {
        ev = new System.Diagnostics.EventLog('<Microsoft Dynamics AX AIF>',
'<Computer Name>',
'<AOS Runtime>');
        try
        {
            entryType = CLRInterop::parseClrEnum('System.Diagnostics.EventLogEntryType',
'<Error>');
            msg = _message;
            ev.WriteEntry(msg, entryType);
        }
        catch
        {
            exceptionTextFallThrough();
        }
        if (ev != null)
        {
            ev.Close();
        }
    }

    CodeAccessPermission::revertAssert();
}

Wednesday, May 23, 2018

Changing Business logic in Standard Microsoft Class methods without overlaying in D365 FO


// Changing Business logic in Standard Microsoft Class methods without overlaying

Tag: Dynamics 365 for finance and operations

Steps:

Scope:
è  Changing logic in Standard Microsoft Class methods without overlaying.

Pre Requisites:
è  Base model package should be reference to current package

Example:
Form->PurchTable
Data Source -> PurchLine
Requirement: Need to validate line amount value in purchLine table from system standard validation class method.

è  Let’s consider this below DEVCOCTEST as an assumption for system standard class and methods.
è  In DEVCOCTEST class, Process method contains validation on table (purchLine).
è  Requirement, need to change one of the intermediate line in process method or else changing or adding extra condition in process method.
Note: In Example code, I have written only few lines.
è  In example, currently overlay is not possible for this class method to do. Only way to achieve through extension and chain of command.
è  As assumption, Overlaying locked by Microsoft

//As per our assumption, this is our Microsoft Standard class and methods
//sample code - MS Hard sealed
Class DEVCOCTEST //Parent Class
{
    static DEVCOCTEST construct(PurchLine _purchLine)
    {
        if(_purchLine)
        {
            return new DEVCOCTEST();
        }
        else
        {
            throw error(Error::wrongUseOfFunction(funcName()));
        }

        return null;
    }

    void  Process(PurchLine _purchLine) //Standard Method
    {
        if(_purchLine.LineAmount > 1000)
        {
            throw warning("Purchase Amount limit exceed _ Parent Class");
        }
    }

}

//This is new customize class, created in new model
//With extends system standard class
Note:
è  Here we need to write method which you want to modify requires  business logic
è  If method not written in child class means then system will automatically call parent class method from child class.
class DEVCOCTEST_COCCHILD extends DEVCOCTEST
{
    public void Process(PurchLine _purchLine)
    {
        if(_purchLine.LineAmount > 1000)
        {
            throw warning("Purchase Amount limit exceed _ Child COC Class");
        }
        else
        {
            super(_purchLine); //if requires
        }
    }

}

//This is new customize class, created in new model
//For Extension of assumed Microsoft standard class (DEVCOCTEST)
Note:
è  Here wherever system is constructing DEVCOCTEST class, based on extension of Microsoft standard class using chain of command. System will hit below code
è  After calling, next command we creating new instance of child class (DEVCOCTEST_COCCHILD)

ExtensionOf(classStr(DEVCOCTEST))]
final class DEVCOCTEST_Extension
{
    static DEVCOCTEST construct(PurchLine _purchLine)
    {
        DEVCOCTEST a = next construct(_purchLine);
        a = new DEVCOCTEST_COCCHILD();
        return a;           
    }

}

//This is only for testing, to call

//Code for Event handler
//This is new customize class
//In example, I have consider FORMDATASOURCEEVENT type as Written
class DEVCOCTESTPURCHTABLEFORMEVENTHANDLER
{
    [FormDataSourceEventHandler(formDataSourceStr(PurchTable, PurchLine), FormDataSourceEventType::Written)]
    public static void PurchLine_OnValidatedWrite(FormDataSource sender, FormDataSourceEventArgs e)
    {  
        var purchLine_ds = sender as FormDataSource;
        PurchLine purchLine = purchLine_ds.cursor() as
            PurchLine;

        if(purchLine)
        {
            DEVCOCTEST  coc =   DEVCOCTEST::construct(purchLine);
            coc.Process(purchLine);
        }
        else
        {
            throw Error("Purchase lines Arguments must be called from journal");
        }
    }

}

Sample tested with
Class\PurchLineType_Purch\


Version: (Dynamics 365 for finance and operations)

Standard Class with example:

//Std PurchLineType_Purch class method - Hard seal
Class: PurchLineType_Purch 
Method: ValidateField

Requirement: Here i need to add one more field validation in switch case
public boolean validateField(FieldId _fieldId)
    {
        boolean      ret;

        ret = super(_fieldId);
        switch (_fieldId)
        {
            case fieldNum(PurchLine, TaxItemGroup) :
            case fieldNum(PurchLine, TaxGroup)     :
                if (purchLine.TaxGroup     &&
                    purchLine.TaxItemGroup &&
                    VendTable::find(purchTable.OrderAccount).vatTaxAgent_RU)
                {
                    ret = Tax::checkVATChargeSource_RU(purchTable.purchTable_RU().vatChargeSource_RU, purchLine.TaxGroup, purchLine.TaxItemGroup);
                }
                break;

            case fieldNum(PurchLine, LineNumber):
                var purchLine_orig = purchLine.orig();

                if (purchLine_orig.RecId                                != 0
                    && purchLine.LineNumber                             != purchLine_orig.LineNumber
                    && PurchParameters::find().DisallowLineRenumbering  == NoYes::Yes)
                {
                    ret = checkFailed("@SCM:RenumberingOfLinesNotAllowed");
                }
                break;
        }

        return ret;
    }


//This is new customize class, created in new model
//With extends system standard class
Note:
è  Here we need to write method which you want to modify requires override business logic of that method
è  If method not written in child class means then system will automatically call parent class method from child class.
class PurchLineType_PurchCOC extends PurchLineType_Purch
{
    public boolean validateField(FieldId _fieldId)
    {
        boolean      ret;
        Price   purchPrice;

        switch (_fieldId)
        {
            //Added - start
            case fieldNum(PurchLine, PurchPrice) :
                purchPrice = InventTableModule::find(PurchLine.itemId,ModuleInventPurchSales::Purch,false).Price;
                if(PurchLine.PurchPrice > purchPrice)
                {
                    throw warning("Purch price shouldn't be greater than, Product default purchase price");
                }
                break;
            //Added - end

            Default : 

                break;
        }

        return ret;
    }
}



//This is new customize class, created in new model
//For Extension of Microsoft standard class (PurchLineType_Purch)
Note:
è  Here wherever system is constructing PurchLineType_Purch class, based on extension object concept System will hit below code
è  After calling, next command we creating new instance of child class (PurchLineType_PurchCOC )

[ExtensionOf(classStr(PurchLineType_Purch))]
final public class PurchLineType_Purch_Extension
{
    static PurchLineType construct(PurchLine purchLine, PurchTable  purchTable)
    {
        PurchLineType_Purch coc = next construct(purchLine,purchLine.purchTable());
        coc = new PurchLineType_PurchCOC(purchLine,purchLine.purchTable());
        return coc;
    }

}

Output:
if Current Purchase line price is valid with default purchase price

If current purchase line price is greater than compare with default purchase price

Tuesday, May 22, 2018

Number sequence customization in Dynamics 365 for Finance and Operations


Create a number sequence in a standard module
  • Create new EDT
  • Add a new code block to the loadModule in the NumberSeqModuleXXX class
  • Add a new static method to the parameters table of the module to get the number sequence reference
Using Extension
[ExtensionOf(classStr(NumberSeqModuleCustomer))]
final class NumberSeqModuleCustTest_Extension
{
            protected void loadModule()
            {
                        NumberSeqDatatype datatype = NumberSeqDatatype::construct();
                        next loadModule();                        
datatype.parmDatatypeId(extendedTypeNum(TestId));
datatype.parmReferenceHelp(literalStr('Test ID'));
                        datatype.parmWizardIsContinuous(false);
                        datatype.parmWizardIsManual(NoYes::No);
                       datatype.parmWizardIsChangeDownAllowed(NoYes::No);
                       datatype.parmWizardIsChangeUpAllowed(NoYes::No);
                        datatype.parmSortField(1);                        
datatype.addParameterType(NumberSeqParameterType::DataArea, true, false);
                        this.create(datatype);
            }
}

[ExtensionOf(tableStr(CustParameters))]
final class CustParametersTest_Extension
{
    client server static NumberSequenceReference numRefTestId()
    {
        return NumberSeqReference::findReference(extendedTypeNum(TestId));
    }
}
Create a number sequence in a new module
·        Add a new element to the NumberSeqModule base enum.
·        Create a NumberSeqModule* class for our module.
·        To add a new element to the NumberSeqModule Base Enum we can use an extension.
·        Now we need to create a new class extends with NumberSeqApplicationModule
class NumberSeqModuleTest extends NumberSeqApplicationModule
{
    protected void loadModule()
    {
        NumberSeqDatatype datatype = NumberSeqDatatype::construct();
        datatype.parmDatatypeId(extendedTypeNum(TestId));
        datatype.parmReferenceHelp(literalStr('Test ID'));
        datatype.parmWizardIsContinuous(false);
        datatype.parmWizardIsManual(NoYes::No);
        datatype.parmWizardIsChangeDownAllowed(NoYes::No);
        datatype.parmWizardIsChangeUpAllowed(NoYes::No);
        datatype.parmSortField(1);       
        datatype.addParameterType(NumberSeqParameterType::DataArea, true, false);
        this.create(datatype);
    }

    public NumberSeqModule numberSeqModule()
    {
        return NumberSeqModule::Test;
    }

    [SubscribesTo(classstr(NumberSeqGlobal),delegatestr(NumberSeqGlobal, buildModulesMapDelegate))]
    static void buildModulesMapSubsciber(Map numberSeqModuleNamesMap)
    {
       NumberSeqGlobal::addModuleToMap(classnum(NumberSeqModuleTest), numberSeqModuleNamesMap);
    }
}

Job: (To load all module number sequences)
static void Test_NumSeqLoad(Args _args)
   {
      NumberSeqApplicationModule::loadAll();
   }  



Skip Inventory reservation on Sales order lines in D365FO

[ExtensionOf(classStr(InventUpd_Reservation))] final class InventUpd_ReservationCls_SAN_Extension {     void updateNow()     {         if(mo...