Wednesday, August 22, 2018

Table Buffer2Buffer by comparing source and target table fields in Dynamics AX


Public static void buffer2buffer(Common _source, Common _target)
    {
        TableScope scope = TableScope::CurrentTableOnly;
        DictTable   sourceDictTable = new DictTable(_source.TableId);
        DictTable   targetDictTable = new DictTable(_target.TableId);

        FieldId     sourceFieldId   = sourceDictTable.fieldNext(0, scope);
        FieldId     targetFieldId   = targetDictTable.fieldNext(0, scope);

        Map targetFields = new Map(Types::String, Types::Int64);

        //Adding target table fields in to Map
        while (targetFieldId && ! isSysId(targetFieldId))
        {
            targetFields.insert(targetDictTable.fieldName(targetFieldId), targetFieldId);
            targetFieldId         = targetDictTable.fieldNext(targetFieldId, scope);
        }  
        
       //Retrieving source field map and comparing with target table fields to set field values
        while (sourceFieldId && ! isSysId(sourceFieldId))
        {
            if (targetFields.exists(sourceDictTable.fieldName(sourceFieldId)))
            {
                targetFieldId = targetFields.lookup(sourceDictTable.fieldName(sourceFieldId));
                _target.(targetFieldId)   = _source.(sourceFieldId);
            } 
            sourceFieldId         = sourceDictTable.fieldNext(sourceFieldId, scope);
        } 
    }


Friday, August 10, 2018

METADATA Objects convert to XML file in AX 2012

Reference: Microsoft Demo data tool

static void MetadataXMLGenrator(Args _args)
{
    Dictionary      dictionary;
    Counter         tableCounter;
    SysDictTable    table;
    Counter         fieldCounter;
    SysDictField    field;
    str             result;

    boolean         isRecID;
    boolean         isTableID;
    boolean         isDatatypeID;
    boolean         isClassID;
    boolean         isFieldID;
    boolean         IsIndexID;
    boolean         isConfigurationKeyId;
    boolean         isEnumId;
    boolean         isLicenseCodeId;
    boolean         isperspectiveId;
    boolean         issecurityKeyId;


    SysModelElement modelElement;
    str             elementType;
    str             tmpfolderpath;
    str             relatedtTableName;

    void write(str _directory, str _name, str _text)
    {
        str path;
        ;

        _text = System.Text.RegularExpressions.Regex::Replace(_text, '\n', '\r\n');
        if (!System.IO.Directory::Exists(_directory))
        {
            System.IO.Directory::CreateDirectory(_directory);
        }
        path = System.IO.Path::Combine(_directory, _name);
        System.IO.File::WriteAllText(path, _text, System.Text.Encoding::get_UTF8());
    }

    boolean isExcluded(SysDictTable _table)
    {
        ;

        return
           _table.isView()
        || _table.isMap()
        || _table.isTmp()
        || _table.isTempDb();
    }

    guid tableOrigin(SysDictTable _table)
    {
        ;
        modelElement = null;
        select firstOnly * from modelElement where modelElement.AxId == _table.id() && modelElement.ElementType == 44;
        if (modelElement)
        {
            return modelElement.Origin;
        }

        return emptyGuid();
    }

    str fmt(guid value, int64 id)
    {
        str digits;
        str o = strReplace(strReplace(guid2str(value), '{', ''), '}', '');

        if (o != '00000000-0000-0000-0000-000000000000')
        {
            return o;
        }

        digits = int642str(id);
        return subStr(o, 0, strLen(o) - strLen(digits)) + digits;
    }

    dictionary = new Dictionary();

    result += '<tables>\n';

    for (tableCounter = 1; tableCounter <= dictionary.tableCnt(); ++tableCounter)
    {
        table = new SysDictTable(Dictionary.tableCnt2Id(tableCounter));

        if (!isExcluded(table))
        {
            result += strFmt('\t<table axid="%1" origin="%2" name="%3" sqlname="%4" %5>\n', table.id(), fmt(tableOrigin(table), table.id()), table.name(), table.name(DbBackend::Sql), table.supportInheritance() ? 'supportsinheritance="true" ' :  '' );

            for (fieldCounter = 1; fieldCounter <= table.fieldCnt(); ++fieldCounter)
            {
                relatedtTableName = '';
                field = new SysDictField(table.id(), table.fieldCnt2Id(fieldCounter));

                if (field.isSql())
                {
                    isRecID = field.isDerivedFrom(extendedTypeNum(RecId));
                    isTableID = field.isDerivedFrom(extendedTypeNum(RefTableId)) || field.isDerivedFrom(extendedTypeNum(RelationType)) || (field.name() == 'InstanceRelationType' && table.supportInheritance() && table.extends() == 0);
                    isDatatypeID = field.isDerivedFrom(extendedTypeNum(ExtendedTypeId));
                    isClassID = field.isDerivedFrom(extendedTypeNum(ClassId));
                    isFieldID = field.isDerivedFrom(extendedTypeNum(FieldId));
                    IsIndexID = field.isDerivedFrom(extendedTypeNum(IndexId));
                    isConfigurationKeyId = field.isDerivedFrom(extendedTypeNum(ConfigurationKeyId));
                    isEnumId = field.isDerivedFrom(extendedTypeNum(EnumId));
                    isLicenseCodeId = field.isDerivedFrom(extendedTypeNum(LicenseCodeId));
                    isperspectiveId = field.isDerivedFrom(extendedTypeNum(PerspectiveId));
                    issecurityKeyId = field.isDerivedFrom(extendedTypeNum(SecurityKeyId));

                    if (isTableID)
                    {
                        elementType = "Table";
                    }
                    else if (isClassID)
                    {
                        elementType = "Class";
                    }
                    else if (isDataTypeID)
                    {
                        elementType = "DataType";
                    }
                    else if (isFieldID)
                    {
                        elementType = "Field";
                        relatedtTableName = field.relatedTableName();
                    }
                    else if (IsIndexID)
                    {
                        elementType = "Index";
                        relatedtTableName = field.relatedTableName();
                    }
                    else if(isConfigurationKeyId)
                    {
                        elementType = "ConfigurationKey";
                    }
                    else if(isEnumId)
                    {
                        elementType = "Enum";
                    }
                    else if(isLicenseCodeId)
                    {
                        elementType = "LicenseCode";
                    }
                    else if(issecurityKeyId)
                    {
                        elementType = "SecurityKey";
                    }
                    else if(isperspectiveId)
                    {
                        elementType = "Perspective";
                    }
                    else
                    {
                        elementType = '';
                    }

                    result += strFmt('\t\t<field axid="%1" origin="%2" name="%3" sqlname="%4" type="%5" %6%7/>\n',
                        field.id(), fmt(field.origin(), field.id()), field.name(), field.name(DbBackend::Sql), field.typeName(),
                        elementType != '' ? ((elementType == ('Field') || elementType == ('Index'))?'elementType="' + elementType + '" '+'relatedTable="'+relatedtTableName + '" ' :'elementType="' + elementType + '" ') : '',
                        isRecID ? 'recid="true" ' : ''
                        );
                }
            }

            result += '\t</table>\n';
        }
    }

    result += '</tables>\n';
    tmpfolderpath = System.Environment::GetEnvironmentVariable('TEMP');
    write(tmpfolderpath, 'Metadata.xml', result);

    info(strFmt("Metadata xml file is created and here is the full path to the file %1\\Metadata.xml",tmpfolderpath));
}

Friday, August 3, 2018

Create and work with custom fields in Dynamics 365 for finance and operations

New Custom workflow in Dynamics 365 for finance and operation


Custom workflow creation in Dynamics 365 for finance and operations
Here example, Creating custom workflow for custom document
Steps:


Workflow creation for existing document in application without Overlaying on canSubmitToWorkflow method 

Suggested workaround solution:  (This can be achieve without overlay)
                  1. Create new table with unique field from custTable and WorkflowStatus Field alone.
                  2. Add newly created table in to form dataSource and link with primary custTable dataSource in FORM: CustTable using extension. 
 Note: Using outer join for join Type, Inner join type works only when there is no record in primary table.
                  3. Override canSubmitToWorkflow method in newly table created above.
                  4. Create new method for FormDataSourceEvent : OnWritten for Primary dataSource (custTable) and create new values for newly created table when record created in primary table.
                  5. UpdateWorfklowStatus method should update status both child Table (New table) and CustTable
                  6. Write method on Datasource On-initialized event method of New table datasource in existing form

Steps:
  •         Create new BASE ENUM (Named: Dev_WorkflowStatus)
  •         Create Extension class for Table (“CustTable”)
  •          Add newly created ENUM in to Table (“CustTable”)
  •         Add new method on CustTable
 Note: CanSubmitToWorkflow here added as overlay method.
It is not advisable from Microsoft. 
Currently canSubmitToWorkflow method through extension is not working as expected in PU12

Note:
-> PU22 (December 2018) - MS Enabled Chain of Command to target method overrides that have not been implemented to tables (now we can use it for canSubmitToWorkflow method).


o   CanSubmitToWorkflow
Code:
public boolean canSubmitToWorkflow(str _workflowType = '')
              {
                     boolean ret = false; 
                     
                     ret = this.DEV_WorkflowStatus == DEV_WorkflowStatus::Draft;
                         
                     return ret;
}
o   UpdateWorkflowStatus
Code:
public static void updateWorkflowStatus(RecId _documentRecId, DEV_WorkflowStatus _status)
{
       CustTable record;
ttsbegin;
update_recordset record setting DEV_WorkflowStatus = _status
where record.RecId == _documentRecId;          
ttscommit;
}
  • ·         Create new query for workflow with data source as CustTable (Named: Dev_CustTableWf_Query)


  • ·         Create new workflow category and mention name & label property


  • ·         Create new workflowType and mention property of DocumentMenuItem, Query, WorklfowCategory, then system create all default objects for workflow type


  • ·         Add pre-requisite method in Submit Manager class

Code:
/// <summary>
/// The Dev_Customer_WfTypeSubmitManager menu item action event handler.
/// </summary>
public class Dev_Customer_WfTypeSubmitManager
{
    public static void main(Args _args)
    {
        // Executes when a work item is submitted.
        RecId                   recId = _args.record().RecId;
        WorkflowSubmitDialog    workflowSubmitDialog;

        // Opens the submit to workflow dialog.
        workflowSubmitDialog = WorkflowSubmitDialog::construct(_args.caller().getActiveWorkflowConfiguration());
        workflowSubmitDialog.run();

        if (workflowSubmitDialog.parmIsClosedOK())
        {
            try
            {
                ttsbegin;
                // Activate the workflow.
                Workflow::activateFromWorkflowType(workFlowTypeStr(<WorkflowType>), recId, workflowSubmitDialog.parmWorkflowComment(), NoYes::No);

                //TODO write logic for ur Workflow document table status field update
                ttscommit;
            }
            catch(exception::Error)
            {
                info('Workflow activation failed');
            }

            // Refresh the caller form.
            FormDataSource callerDS = FormDataUtil::getFormDataSource(_args.record());
            callerDS.reread();
            callerDS.refresh();
            _args.caller().updateWorkflowControls();
        }
    }

}

  • Add pre-requisite method in ReSubmit Manager class

Code:
/// <summary>
/// The Dev_Customer_WfTypeReSubmitManager menu item action event handler.
/// </summary>
public class Dev_Customer_WfTypeReSubmitManager
{
public static void main(Args _args)
    {
        // Executes when a work item is resubmitted.
        RecID                   recID       = _args.record().RecId;
        WorkflowWorkItemTable   workItem    = _args.caller().getActiveWorkflowWorkItem();
       
        if (workItem)
        {
            try
            {
                // Opens the resubmit to workflow dialog.
                WorkflowWorkItemActionDialog dialog = WorkflowWorkItemActionDialog::construct(
                    workItem,
                    WorkflowWorkItemActionType::Resubmit,
                    new MenuFunction(_args.menuItemName(),_args.menuItemType()));
                dialog.run();
                if (dialog.parmIsClosedOK())
                {
                    ttsbegin;
                    workItem = _args.caller().getActiveWorkflowWorkItem();
                    WorkflowWorkItemActionManager::dispatchWorkItemAction(
                        workItem,
                        dialog.parmWorkflowComment(),
                        dialog.parmTargetUser(),
                        WorkflowWorkItemActionType::Resubmit,
                        _args.menuItemName());
                   
                   //TODO write logic for ur workflow document class status update logic
                    ttscommit;
                }
            }
            catch(exception::Error)
            {
                info('Workflow resubmission failed');
            }
        }
        // Refresh the caller form.
        FormDataSource callerDS = FormDataUtil::getFormDataSource(_args.record());
        callerDS.reread();
        callerDS.refresh();
        _args.caller().updateWorkflowControls();
    }
}

  • ·         Go to event handler created from workflow Type and follow below code

public class  Dev_Customer_WfTypeEventHandler implements WorkflowCanceledEventHandler,  WorkflowCompletedEventHandler, WorkflowStartedEventHandler
{
    public void started(WorkflowEventArgs _workflowEventArgs)
    {
        RecId documentRecId = _workflowEventArgs.parmWorkflowContext().parmRecId();
        CustTable::updateWorkflowStatus(documentRecId, DEV_WorkflowStatus::Submitted);
    }

    public void canceled(WorkflowEventArgs _workflowEventArgs)
    {
        RecId documentRecId = _workflowEventArgs.parmWorkflowContext().parmRecId();
        CustTable::updateWorkflowStatus(documentRecId, DEV_WorkflowStatus::Rejected);
    }

    public void completed(WorkflowEventArgs _workflowEventArgs)
    {
        RecId documentRecId = _workflowEventArgs.parmWorkflowContext().parmRecId();
        CustTable::updateWorkflowStatus(documentRecId, DEV_WorkflowStatus::Approved);
    }

}
  • ·         Create new workflow approval




  • ·         Add logic on workflow approval event handler class

For demo purpose, we have added code only for cancelled, completed, Change Request Methods
Code:
/// <summary>
/// The Dev_Customer_WfAppEventHandler workflow outcome event handler.
/// </summary>
public final class Dev_Customer_WfAppEventHandler implements WorkflowElementCanceledEventHandler,
            WorkflowElemChangeRequestedEventHandler,
            WorkflowElementCompletedEventHandler,
            WorkflowElementReturnedEventHandler,
            WorkflowElementStartedEventHandler,
            WorkflowElementDeniedEventHandler,
            WorkflowWorkItemsCreatedEventHandler
{
    public void canceled(WorkflowElementEventArgs _workflowElementEventArgs)
    {
        RecId documentRecId = _workflowElementEventArgs.parmWorkflowContext().parmRecId();
        CustTable::updateWorkflowStatus(documentRecId, DEV_WorkflowStatus::Rejected);
    }

    public void completed(WorkflowElementEventArgs _workflowElementEventArgs)
    {
        RecId documentRecId = _workflowElementEventArgs.parmWorkflowContext().parmRecId();
        CustTable::updateWorkflowStatus(documentRecId, DEV_WorkflowStatus::Approved);
    }

    public void denied(WorkflowElementEventArgs _workflowElementEventArgs)
            {
                        // TODO:  Write code to execute once the workflow is denied.
            }

    public void changeRequested(WorkflowElementEventArgs _workflowElementEventArgs)
            {
                        RecId documentRecId = _workflowElementEventArgs.parmWorkflowContext().parmRecId();
        CustTable::updateWorkflowStatus(documentRecId, DEV_WorkflowStatus::RequestChange);
            }
}
  • ·         Add newly created workflow approvals to Workflow type created for this custom document workflow


Note: Like this we can add automated Task and mapping line item workflow as per your requirement.
·         Assign workflowType, workflowEnable and WorkflowDatasource on Document FORM
Note:
If properties enabled in FORM means, Assign there itself
Default Application Suite is Hard locked, no more overlay and currently system is not allowing to edit form design property in Form:CustTable through extensibility also.
Or else written code on Form Document datasource OnInitialized Method as follow
class Dev_Customer_Wf_EventHandler
{   
    /// <summary>
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    [FormDataSourceEventHandler(formDataSourceStr(CustTable, CustTable), FormDataSourceEventType::Initialized)]
    public static void CustTable_OnInitialized(FormDataSource sender, FormDataSourceEventArgs e)
    {         
        sender.formRun().design().workflowDatasource("CustTable");
        sender.formRun().design().workflowEnabled(true);
        sender.formRun().design().workflowType("Dev_Customer_WfType");
    }
}
  • ·         Navigate to document, Verify new workflow for customer document.
  • ·         Added new field in to document form
  • ·         Project Elements (New Workflow)


  • Navigate to workflow configuration on Module/Setup/Workflows and create new by selecting new workflow type

  • Create new customer to verify workflow
  • Rebuild solution

Thursday, August 2, 2018

Enabling workflow related properties on FORM design in Dynamics 365 for finance and operations

Enabling workflow related properties on FORM design in Dynamics 365 for finance and operations

Achieve workflow feature  enabling using extensions without overlay.

[FormDataSourceEventHandler(formDataSourceStr(CustTable, CustTable), FormDataSourceEventType::Initialized)]
public static void CustTable_OnInitialized(FormDataSource sender, FormDataSourceEventArgs e)
{
    sender.formRun().design().workflowDatasource("CustTable");
    sender.formRun().design().workflowEnabled(true);
    sender.formRun().design().workflowType("CustTable_DevWkType");
}

Accessing AXFORM global variables in EventHandler methods Dynamics 365 for finance and operations


//Accessing AX FORM global variables in EventHandler static methods in Dynamics 365 for finance and operations

Note: This is only for privately declared variables in FORM


Step 1:
[ExtensionOf(formStr(PurchTable))]
final class DevFormPurchTable_Extension
{
PurchTableType parmpurchTableType()
{
return purchTableType;
}
}

Step 2:
class DevPurchTableEventHandler
{
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
[FormControlEventHandler(formControlStr(PurchTable, PurchCreateFromSalesorderPurchase), FormControlEventType::Clicked)]
public static void PurchCreateFromSalesorderPurchase_OnClicked(FormControl sender, FormControlEventArgs e)
{
FormRun formRun = sender.formrun();
PurchTableType purchTableTypeGet;
purchTableTypeGet = formRun.parmpurchTableType();
info(strFmt("%1",purchTableTypeGet.parmPurchTable().PurchId));
}
}


Convert Call stack to readable format in D365FO X++

//Input --container _xppCallStack = xSession::xppCallStack();  Public static str POL_formatXppCallStack(container _xppCallStack, int _skipFr...