Sunday 22 March 2020

get form datasource object in an Eventhandler in Dynamics 365 Finance X++

get a data-source in the event-handler for form method 'closeok', using post event.


You can change the code for other form methods in the same way.



class SalesEditLines_EventHandler

{

    /// <summary>

    ///

    /// </summary>

    /// <param name="args"></param>

    [PostHandlerFor(formStr(SalesEditLines), formMethodStr(SalesEditLines, closeOk))]

    public static void SalesEditLines_Post_closeOk(XppPrePostArgs args)

    {

        FormRun sender = args.getThis();

        SalesParmUpdate salesParmUpdate;

        salesParmUpdate = sender.dataSource(formdatasourcestr(SalesEditLines, SalesParmUpdate)).cursor();



        info(salesParmUpdate.ParmId);

    }



}



Reference: 

https://rahulmsdax.blogspot.com/2019/05/how-to-get-form-datasource-object-in.html

Update_recordset Taking long time / Not responding

skip databaselog,datamethods,events before Update_Recordset.
Ex: wmsOrderTrans.skipDatabaseLog(true);
    wmsOrderTrans.skipDataMethods(true);
    wmsOrderTrans.skipEvents(true); 
Example Code:
static void Job250(Args _args)
{
    WMSOrderTrans           wmsOrderTrans;
    InventDim               inventDimCur;
    InventDim               inventDimPackSlip;
    InventTrans             inventTransPackSlip;
    ;
    wmsOrderTrans.skipDatabaseLog(true);
    wmsOrderTrans.skipDataMethods(true);
    wmsOrderTrans.skipEvents(true);
    update_recordset wmsOrderTrans setting BBBPackingSlipExists  = NoYes::Yes
        where wmsOrderTrans.isReserved
        exists join inventTransPackSlip
        where inventTransPackSlip.InventTransId == wmsOrderTrans.inventTransId
          && (inventTransPackSlip.StatusIssue == StatusIssue::Deducted
           || inventTransPackSlip.StatusIssue == StatusIssue::Sold)
          && !inventTransPackSlip.PackingSlipReturned
        exists join inventDimCur
        where inventDimCur.inventDimId == wmsOrderTrans.inventDimId
        exists join inventDimPackSlip
        where inventDimPackSlip.inventDimId == inventTransPackSlip.inventDimId
           && inventDimCur.inventSerialId  == inventDimPackSlip.inventSerialId;       
}

Saturday 21 March 2020

Form Filter by Execute Query


1) GO to FORM--> Datasource--> Events---> OnQueryExecuting--> Copy EventHandlerMEthod
2) Create Class by Extension of FormDataSource(FormName, DatasourceName))]
     Final class XXXXX_Extension
{
 Paste Copied Eventhandler here and change according to example below.
}

EXAMPLE:

[Extensionof(FormDataSourcestr(SysUserManagement, UserInfo))]
final class SYSUSERINFO_Extension
{     


    /// <summary>
    ///
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
 

    [FormDataSourceEventHandler(formDataSourceStr(SysUserManagement, UserInfo), FormDataSourceEventType::QueryExecuting)]

    public static void UserInfo_OnQueryExecuting(FormDataSource sender, FormDataSourceEventArgs e)

    {

        FormRun          SysUserManagementForm = sender.formRun();

        sender.query().dataSourceName(sender.name()).addRange(fieldnum(UserInfo, Company)).value(queryValue('USMF'));
     

    }

}

Thursday 12 March 2020

Form & Table Event Handlers

Form data source event handler

 [FormDataSourceEventHandler(formDataSourceStr(EcoResProductDetailsExtended, InventTable), FormDataSourceEventType::Written)]
public static void InventTable_OnWritten(FormDataSource sender, FormDataSourceEventArgs e){
    FormRun                 form           = sender.formRun();
    FormDataSource          InventTable_ds =       form.dataSource(formDataSourceStr(EcoResProductDetailsExtended,InventTable)) as FormDataSource;
   InventTable             inventTable    = InventTable_ds.cursor();
}

Form event handler Table Buffer on form closing event

[FormEventHandler(formStr(EcoResAttributeValue), FormEventType::Closing)]
public static void EcoResAttributeValue_OnClosing(xFormRun sender, FormEventArgs e)
{
     FormDataSource ecoResProduct_ds   =          sender.dataSource(formDataSourceStr(EcoResAttributeValue, EcoResProductAttributeValue));
      EcoResProductAttributeValue      ecoResAttributeValue = ecoResProduct_ds.cursor();
}   
Control value and form event level for which auto declaration must be set true

[FormControlEventHandler(formControlStr(EcoResProductCreate, OKButton), FormControlEventType::Clicked)]
public static void OKButton_OnClicked(FormControl sender, FormControlEventArgs e)
{
       FormRun             element       = sender.formRun();
       //form control
       FormControl         modelGroupRef = element.design(0).controlName("ModelGroupId");
        Info(strfmt(“Model Group %1”, modelGroupRef.valueStr()));
       //form parameter
       ItemId              itemId        = element.parmItemId();
}

Post handler for class method

[PostHandlerFor(classStr(EcoResProductReleaseManager), methodStr(EcoResProductReleaseManager, release))]
public static void EcoResProductReleaseManager_Post_release(XppPrePostArgs args){
     EcoResProductReleaseManager releaseMgr;
    //Getting the class object
    releaseMgr     = args.getThis();
   //Getting the class parameter
   ItemId itemId  = releaseMgr.parmItemId();
   //Getting the method argument
    boolean itemCreation = args.getArg("_isCreation");
}

Post handler for overriding table methods modified field and validate Write

[PostHandlerFor(tableStr(InventTable), tableMethodStr(InventTable, validateWrite))]
public static void InventTable_Post_validateWrite(XppPrePostArgs args)
{
      InventTable inventTable = args.getThis() as InventTable
      boolean ret = true;
      // Override the validations here and set the return value accordingly.
       Args.setReturnValue(ret);
}

[PostHandlerFor(tableStr(InventTable), tableMethodStr(InventTable, modifiedField))]
public static void InventTable_Post_modifiedField(XppPrePostArgs args)
{
        //Getting the table buffer
        InventTable inventTable = args.getThis() as InventTable
       //Getting the field id method argument.
        FieldId fieldModified = args.getArg("_fieldId");
        switch (fieldModified)
        {
            //Here you can write your logic on modified field method
                break;
        }
}
1. Form dataSource event handler:  will be using the vendTable form to get table buffer and perform additional business logic or validation.
Vendtable = Form namevendTable = form data source name
[FormDataSourceEventHandler(formDataSourceStr(VendtablevendTable), FormDataSourceEventType::Written)]
public static void vendTable_OnWritten(FormDataSource sender, FormDataSourceEventArgs e)
{
    FormRun                 form           = sender.formRun();
    FormDataSource          vendTable_ds =       form.dataSource(formDataSourceStr(Vendtable,Vendtable)) as FormDataSource;
   Vendtable     Vendtable= vendTable_ds.cursor();
<Now to got table buffer, perform business logic/validation>
}
lets see one more example below;

2. Form DataSource while closing the form (same will apply for OnInitialized,
[FormEventHandler(formStr(EcoResAttributeValue), FormEventType::Closing)]
public static void EcoResAttributeValue_OnClosing(xFormRun sender, FormEventArgs e)
{
     FormDataSource ecoResProduct_ds   =          sender.dataSource(formDataSourceStr(EcoResAttributeValue, EcoResProductAttributeValue));
      EcoResProductAttributeValue      ecoResAttributeValue = ecoResProduct_ds.cursor();
<YOUR CODE>
}   

3.  Post-event hander of Form init or any other standard method method
[[PostHandlerFor(formStr(Ledger), formMethodStr(Ledger, init))]
    public static void Ledger_Post_init(XppPrePostArgs _args)
    {
        #ISOCountryRegionCodes
        FormRun form = _args.getThis();
        FormDesign design = form.design();
        FormControl revaluationAccount = design.controlName(formControlStr(Ledger, revaluationAccount));
        FormControl currencyRevaluation = design.controlName(formControlStr(Ledger, currencyRevaluation));

<your code>
}

4. Form control event hander:
[FormControlEventHandler(formControlStr(LogisticsPostalAddress, Roles), FormControlEventType::Modified)]
    public static void Roles_OnModified(FormControl sender, FormControlEventArgs e)
    {
        FormRun element = sender.formRun();
        FormControl purposeCtrl   = element.design().controlName(formControlStr(LogisticsPostalAddress, Roles));
   
        FormDataSource logisticsLocation_DS = element.dataSource(formDataSourceStr(LogisticsPostalAddress, LogisticsLocation));
       <business logic>
    }


Table Event Handlers:  OnValidateField
[DataEventHandler(tableStr(CustTable), DataEventType::ValidatedField)]

public static void CustTable_onValidatedField(Common sender, DataEventArgs e)
{
ValidateFieldEventArgs  event = e as ValidateFieldEventArgs ;
CustTable custTable = sender as CustTable;
boolean result = event.parmValidateResult();
result = result && RegNumberValidator_BG::validate(custTable.AccountNum, custTable.RegistrationNumber_BG, CustVendACType::Cust);
event.parmValidateResult(result);
}

Table Event Handlers:  OnValidateField
[DataEventHandler(tableStr(InventLocation), DataEventType::ValidatedField)]
public static void InventLocation_onValidatedField(Common sender, DataEventArgs e)
{
InventLocation inventLocation = sender as InventLocation;
ValidateFieldEventArgs fieldArgs = e;
boolean ret;
InventLocation inventLocationLoc;
switch(fieldArgs.parmFieldId())
{
case fieldNum(InventLocation, field1):
if(inventLocation.MyWorkerAssociate != '')
{
<Your code/ business ogic/validation>
<ret = true or false>
fieldArgs.parmValidateResult(ret);
}
}
}

Table Event Handlers:  ModifiedField

[DataEventHandler(tableStr(PurchAgreementHeader), DataEventType::ModifiedField)]
public static void PurchAgreementHeader_onModifiedField(Common sender, DataEventArgs e)
{
ModifyFieldEventArgs    event = e as DataEventArgs;
PurchAgreementHeader    purchAgreementHeader = sender as PurchAgreementHeader;
FieldId                 fieldId = event.parmFieldId();
date                    emptyDate;
switch(fieldId)
{
case fieldNum(PurchAgreementHeader, BankGuarantee):
if(purchAgreementHeader.BankGuarantee == NoYes::No)
{
purchAgreementHeader.BankGuaranteeExpirationDate = emptyDate ;
purchAgreementHeader.BankGuaranteeNumber         = “”;
purchAgreementHeader.Bank                        = “”;
}
break;
}
}



*********************Form Event Handlers*************************************
[FormEventHandler(formStr(HcmPosition), FormEventType::Initialized)]
        public static void HcmPosition_OnInitialized(xFormRun sender, FormEventArgs e)
        {

            FormDataSource hcmosition_ds = sender.dataSource(formDataSourceStr(HcmPosition, HcmPosition));
 Or 
FormDataSource                              hcmosition_ds = sender.dataSource('HcmPosition');
        }

If you copy form datasource event you can get form run object from datasource  as below. Once you get form run you can call any form method available on the form.
[FormDataSourceEventHandler(formDataSourceStr(HcmPosition, HcmPosition), FormDataSourceEventType::Created)]
        public static void HcmPosition_OnCreated(FormDataSource sender, FormDataSourceEventArgs e)
        {

            FormRun formRun = sender.formRun() as FormRun;
            
        }

Similarly you can get formrun on a from control event handler using FormControl
[FormControlEventHandler(formControlStr(HcmPosition, HcmPosition_PositionId1), FormControlEventType::Modified)]
        public static void HcmPosition_PositionId1_OnModified(FormControl sender, FormControlEventArgs e)
        {
            FormRun formRun = sender.formRun() as FormRun;

        }
To get control on a form  and make it editable or non-editable
[FormEventHandler(formStr(HcmPosition), FormEventType::Initialized)]
        public static void HcmPosition_OnInitialized(xFormRun sender, FormEventArgs e)
        {
            sender.design().controlName(formControlStr(HcmPosition, HcmPositionNewPosition)).AllowEdit(false);
            // to get form open Mode
            OpenMode                                    openMode = sender.args().openMode();
        }

Getting  current record
Hcmposition is first datasource  on HcmPosition form  that’s why  gave 1 in datasource.
[FormControlEventHandler(formControlStr(HcmPosition, HcmPositionNewPosition), FormControlEventType::Clicked)]
        public static void HcmPositionNewPosition_OnClicked(FormControl sender, FormControlEventArgs e)
        {

            HcmPosition hcmposition = sender.formRun().dataSource(1).cursor();
        }

Using DataEventArgs to send Validation result
[DataEventHandler(tableStr(CategoryTable), DataEventType::ValidatingDelete)]
    public static void CategoryTable_onValidatingDelete(Common _sender, DataEventArgs _e)
    {
        CategoryTable categoryTable = _sender as CategoryTable;
        ValidateEventArgs validateEventArgs = _e as ValidateEventArgs;
        boolean ret = true;

        if (categoryTable.UseInProject)
        {
            ProjCategory projCategory = ProjCategory::find(categoryTable.CategoryId);
            ret = projCategory.validateDelete();
        }

        if (ret && categoryTable.UseInExpense)
        {
            TrvCostType trvCostType = TrvCostType::find(categoryTable.CategoryId);
            ret = trvCostType.validateDelete();
        }
        
        if (!ret)
        {
            validateEventArgs.parmValidateResult(false);
        }
    }

Similarly you can use on ValidateFieldValueEventArgs  to send validation result back to Validate Field method
[DataEventHandler(tableStr(LedgerParameters), DataEventType::ValidatingFieldValue)]
    public static void LedgerParameters_onValidatingFieldValue(Common sender, DataEventArgs e)
    {
        ValidateFieldValueEventArgs ve = e;
        boolean isValid = true;
        LedgerParameters ledgerParameters = sender as LedgerParameters;
        #isoCountryRegionCodes
        
        if (ve.parmFieldName() == fieldStr(LedgerParameters, ChineseVoucher_CN) 
            && SysCountryRegionCode::isLegalEntityInCountryRegion([#isoCN]))
        {
            if ((select firstonly RecId from LedgerJournalTrans
                    where LedgerJournalTrans.LedgerVoucherType_CN != 0
                        || LedgerJournalTrans.Voucher_CN != '').RecId != 0)
            {
                // The general journal needs to be empty in order to modify the setup for the Chinese voucher system.
                isValid = checkFailed("@GLS54497");
            }
            ve.parmValidateResult(isValid);
        }
        
    }


Reference : https://community.dynamics.com/365/financeandoperations/b/365operationswithsukrut/posts/customizing-d365-with-event-handlers

How to use Form event ‘OnInitialized’ in Dynaics365


Scenario: On from ‘ProjAdjustmentSplit’ I want to enable/disable a field on certain condition. As D365 urge to not to customize ant standard object than write EventHandlers to achieve your requirement. In earlier version of AX, we simple write this logic either on Form Active method or from DS init method. Here we have to use EventHandlder for the same.  Here we will use ‘OnInitialized’ method and will write an eventHandler for the same.
image

Let’s take a look.

Approach: Follow below step to get this done,

Step 1: Right click on “OnInitialized’ method and select ‘Copy event handler method’

image


Step 2: Create a new class and paste this method.
/// <summary>
///
     /// </summary>
     /// <param name="sender"></param>
     /// <param name="e"></param>
     [FormDataSourceEventHandler(formDataSourceStr(ProjAdjustmentSplit, TmpProjAdjustmentSplit), FormDataSourceEventType::Initialized)]
     public static void TmpProjAdjustmentSplit_OnInitialized(FormDataSource sender, FormDataSourceEventArgs e)
     {
}

Step 3: Use below code to access form object
TmpProjAdjustmentSplit      tmpProjAdjustmentSplit         = sender.cursor();
         FormDataSource              tmpProjAdjustmentSplit_ds      = sender.formRun().dataSource('TmpProjAdjustmentSplit');
         FormRun                     element                     = sender.formRun();
         FormControl                 projID;
    
         projID= element.design(0).controlName("TmpProjAdjustmentSplit_ProjId");

Step 4: After above declaration now wrtie your actual business logic.
if(ProjParameters::find().myParameter== NoYes::Yes)
         {
             projID.allowEdit(false);
         }
         else
         {
             projID.allowEdit(true);
         }
Step 5: Now go to
Project management and accounting > Periodic > Transactions > Adjust transactions and click on Adjust button to test your code.

Note: Try to add use the same code on your form and add more field using formConrol. Make sure you mark this control AutoDeclare = Yes on Form design

Wednesday 11 March 2020

Extend ‘canSubmitToWorkflow’ in standard & base tables without over-layering

This is how we can avoid over-layering while creating workflow on standard form.
CanSubmitToWorkflow
-> Extension of ‘CanSubmitToWorkflow’ method in standard table which is not overridden by standard:
 ‘CanSubmitToWorkflow’ is being called from FormDataUtil:: CanSubmitToWorkflow() method for which post event handler can be written, which is purely extension.

Following is the code snippet:
    /// <summary>
    /// Can submit to workflow
    /// </summary>
    /// <param name="args"></param>
    [PostHandlerFor(classStr(FormDataUtil), staticMethodStr(FormDataUtil, canSubmitToWorkflow))]
    public static void FormDataUtil_Post_canSubmitToWorkflow(XppPrePostArgs args)
    {
        Common             record               = args.getArg(identifierStr(_record));
        SalesTable          salesTable           = record as SalesTable;
        boolean            ret                  = args.getReturnValue();

        if (record.TableId == tableNum(SalesTable))
        {
            if (salesTable.WorkFlowStatus == VersioningDocumentState::Draft)
            {
                ret = boolean::true;
            }
            else
            {
                ret = boolean::false;
            }
        }
        args.setReturnValue(ret);                    
    }

-> Changing workflow properties in standard form through extension:
·         Copy On Initializing event handler of form events.
·         Paste it in your event handler class
·         Write body of the method as follows (replace workflow datasource and workflow type)
     /// <summary>
    /// Triggering workflow dynamically
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    [FormEventHandler(formStr(SalesTableListPage), FormEventType::Initializing)]
    public static void SalesTableListPage_OnInitializing(xFormRun sender, FormEventArgs e)
    {
        FormRun                SalesTableListPage         = sender;
        FormBuildDesign SalesTableListPageDesign = SalesTableListPage.form().design();

        SalesTableListPageDesign.workflowEnabled(true);
        SalesTableListPageDesign.workflowDatasource(tableStr(SalesTable));
        SalesTableListPageDesign.workflowType(workflowTypeStr(SalesOrderWFType));
    }
************************************************************************
To enable workflow in SalesTable form, we need to write below code.
[PostHandlerFor(classStr(SalesTableInteraction), methodStr(SalesTableInteraction, enableHeaderActions))]
   public static void SalesTableInteraction_Post_enableHeaderActions(XppPrePostArgs args)
   {
       SalesTable activeRecord;
       SalesTableInteraction        interaction        = args.getThis();
       Page salesTablePage = interaction.page();
       if (salesTablePage.activeRecord(tablestr(SalesTable)))
       {            
           FormRun formrun   = salesTablePage.formRun();
           FormBuildDesign design     = formrun.form().design();
           FormDesign        wfDesign = formrun.design();
           //WF setup
           design.workflowEnabled(true);
           design.workflowDatasource(tableStr(SalesTable));
           design.workflowType(workflowTypeStr(LTSalesOrderWFType));
           wfDesign.workflowEnabled(true);
           wfDesign.workflowDatasource(tableStr(SalesTable));
           wfDesign.workflowType(workflowTypeStr(LTSalesOrderWFType));
   //updating wf controls            
           formrun.updateWorkflowControls();
       }
   }
To  make workflow button visible in SalesTable form, we need to write below code.
[PostHandlerFor(formStr(SalesTable), formMethodStr(SalesTable, canSubmitToWorkflow))]
   public static void SalesTable_Post_canSubmitToWorkflow(XppPrePostArgs args)
   {
       FormRun salesTableDetails = args.getThis();
       formDataSource salesTableDS    = salesTableDetails.dataHelper().FindDataSource(formDataSourceStr(SalesTable, SalesTable));
       SalesTable salesTable    = salesTableDS.cursor();
       boolean ret    = args.getReturnValue();
       if (salesTable.LTWorkFlowStatus == VersioningDocumentState::Draft)
      {
           ret = NoYes::True;
       }
       else
       {
           ret = NoYes::false;
       }
       args.setReturnValue(ret);
       salesTableDetails.design().controlName('WorkflowActionBarButtonGroup').visible(true);
   }
************************************************************************
Example : 2 
Requirement
There is no standard workflow feature in available process ‘Request For Quotations’.There is a requirement of enabling customised workflow in standard process called ”and base table ‘PurchRFQCaseTable’ and form ‘PurchRFQCaseTableListPage’
Issue
In current version of D365/AX7, there is no possible way to use or extend method ‘canSubmitToWorkflow’ in standard table like PurchRFQCaseTable without overlayering.Also there is no event listed in table PurchRFQCaseTable related to ‘canSubmitToWorkflow’.
Sample Code and Instructions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[ExtensionOf(ClassStr(FormDataUtil))]
final class ACXFormDataUtil_Extension
{
public static boolean canSubmitToWorkflow(Common _record, str _workflowType)
{
boolean ret = next canSubmitToWorkflow(_record, _workflowType);
if (_record.TableId == tableNum(PurchRFQCaseTable))
return true;
else
return ret;
}
}

POSTMAN D365

  Postman is useful to test the behavior of different OData class from/to D365FO. In this post we will see the steps to setup Postman with D...