Monday, February 16, 2026

Custom Workflow in D365fo

In this blog we see all about Workflows development in D365fo 

Output as shown in below


We will see in this blog first way

Step1:-Create a base enum with elements as shown in below and name it as Workflowstatus
Step2:-Add it in the table as well as form as shown in below



Step3:-Write code in canSubmitToWorkFlowmethod in Table level , This will make you to submit the workflow in its is Draft allow.

Code:-
public boolean canSubmitToWorkFlow(str _workflowType = "")  
  {        
      boolean ret;           
      ret = super(_workflowType);       
    if (this.WorkflowStatus == WorkflowStatus::Draft)    
    {        
       ret = true;       
    }         
    return ret;   
}
And also write new method for updating workflow status

Code:-
public static void  updateWorkFlowStatus(RefRecId _recId , WorkflowStatus _status)   
   {   
     EmployeeTable   employeeTable;    
      ttsbegin;           
     select firstonly forupdate employeeTable        
      where employeeTable.RecId == _recId;      
    
employeeTable.EmployeeWFStatus = _status;    
    employeeTable.update();       
    ttscommit;    
}

Step4:-Add new Query and drag table into datasource and make Dynamicfields property to YES 
as shown in below
Step 5:-Create a Workflow category and give Module name in property and this will make workflow type available in frontend to configure the workflow steps.Here i am giving Vendor to visible 
in Accounts payable workflows
Step 6:-Add Workflow Type 
And Provide all details created earlier as shown in below:-

Click on next then system itself creates a event handlers, classes as shown in below
Step 7:-Write code in Workflowtype eventhandler class

Code:-
          /// <summary>

/// The CustomWorkflowTypeEventHandler workflow event handler.
/// </summary>
public class  CustomWorkflowTypeEventHandler implements WorkflowCanceledEventHandler,  
  WorkflowCompletedEventHandler,
  WorkflowStartedEventHandler
{
    public void started(WorkflowEventArgs _workflowEventArgs)
  {
    // TODO:  Write code to execute once the workflow is started.
        EmployeeTable::updateWorkFlowStatus(_workflowEventArgs.parmWorkflowContext().parmRecId(), WorkflowStatus::Pending);
  }

 

    public void canceled(WorkflowEventArgs _workflowEventArgs)
  {
    // TODO:  Write code to execute once the workflow is canceled.
        EmployeeTable::updateWorkFlowStatus(_workflowEventArgs.parmWorkflowContext().parmRecId(), WorkflowStatus::Draft);
  }

 

    public void completed(WorkflowEventArgs _workflowEventArgs)
  {
    // TODO:  Write code to execute once the workflow is completed.
        EmployeeTable::updateWorkFlowStatus(_workflowEventArgs.parmWorkflowContext().parmRecId(), WorkflowStatus::Completed);
  }

 

}

Step8:- Add code in Submitmanagerclass

Code:- 

          /// <summary>

/// The CustomWorkflowTypeSubmitManager menu item action event handler.
/// </summary>
public class CustomWorkflowTypeSubmitManager 
{
    public static void main(Args _args)
  {
    //  TODO:  Write code to execute once a work item is submitted.

 

        EmployeeTable                 employeetable;
        CustomWorkflowTypeSubmitManager    submitManger = new CustomWorkflowTypeSubmitManager();
        recId                              _recId =_args.record().RecId;
        WorkflowCorrelationId         _workflowCorrelationId;
        workflowTypeName           _workflowTypeName = workFlowTypeStr("CustomWorkflowType");
        WorkflowComment              note = "";
        WorkflowSubmitDialog        workflowSubmitDialog;

 

        workflowSubmitDialog = WorkflowSubmitDialog::construct(_args.caller().getActiveWorkflowConfiguration());
        workflowSubmitDialog.run();

        if (workflowSubmitDialog.parmIsClosedOK())
        {
            employeetable = _args.record();
            note = workflowSubmitDialog.parmWorkflowComment();
            try
            {
                ttsbegin;
                _workflowCorrelationId = Workflow::activateFromWorkflowType(_workflowTypeName, employeetable.RecId, note, NoYes::No);
                employeetable.WorkflowStatus = WorkflowStatus::Submitted;
                employeetable.update();
                ttscommit;

                info("Submitted to workflow.");
            }
            catch (Exception::Error)
            {

                error("Error on workflow activation.");
            }
        }
        _args.caller().updateWorkFlowControls();
  }

 

}

Step 9:-Add Workflow Approval 

Provide details created in above steps


After clicking next system will create some classes and action menuitems

Step10:-Add code in Approval event handler

Code:-

       /// <summary>

/// The CustomWorkflowApprovalEventHandler workflow outcome event handler.
/// </summary>
public final class CustomWorkflowApprovalEventHandler implements WorkflowElementCanceledEventHandler,
  WorkflowElemChangeRequestedEventHandler,
  WorkflowElementCompletedEventHandler,
  WorkflowElementReturnedEventHandler,
  WorkflowElementStartedEventHandler,
  WorkflowElementDeniedEventHandler,
  WorkflowWorkItemsCreatedEventHandler
{
    public void started(WorkflowElementEventArgs _workflowElementEventArgs)
  {
    // TODO:  Write code to execute once the workflow is started.
        EmployeeTable::updateWorkFlowStatus(_workflowElementEventArgs.parmWorkflowContext().parmRecId(), WorkflowStatus::InReview);
  }

 

    public void canceled(WorkflowElementEventArgs _workflowElementEventArgs)
  {
    // TODO:  Write code to execute once the workflow is canceled.
        EmployeeTable::updateWorkFlowStatus(_workflowElementEventArgs.parmWorkflowContext().parmRecId(), WorkflowStatus::Draft);
  }

 

    public void completed(WorkflowElementEventArgs _workflowElementEventArgs)
  {
    // TODO:  Write code to execute once the workflow is completed.
        EmployeeTable::updateWorkFlowStatus(_workflowElementEventArgs.parmWorkflowContext().parmRecId(), WorkflowStatus::Completed);
  }

 

    public void denied(WorkflowElementEventArgs _workflowElementEventArgs)
  {
    // TODO:  Write code to execute once the workflow is denied.
        EmployeeTable::updateWorkFlowStatus(_workflowElementEventArgs.parmWorkflowContext().parmRecId(), WorkflowStatus::Rejected);
  }

 

    public void changeRequested(WorkflowElementEventArgs _workflowElementEventArgs)
  {
    // TODO:  Write code to execute once change is requested for the workflow.
        EmployeeTable::updateWorkFlowStatus(_workflowElementEventArgs.parmWorkflowContext().parmRecId(), WorkflowStatus::ChangeRequest);
  }

 

    public void returned(WorkflowElementEventArgs _workflowElementEventArgs)
  {
    // TODO:  Write code to execute once the workflow is returned.
        EmployeeTable::updateWorkFlowStatus(_workflowElementEventArgs.parmWorkflowContext().parmRecId(), WorkflowStatus::Rejected);
  }

 

    public void created(WorkflowWorkItemsEventArgs _workflowWorkItemsEventArgs)
  {
    // TODO:  Write code to execute once work items are created.
  }

 

}

Step11:-Write code in Resubmit class

/// <summary>
/// The CustomWorkflowApprovalResubmitActionMgr menu item action event handler.
/// </summary>
public class CustomWorkflowApprovalResubmitActionMgr 
{
    public static void main(Args _args)
  {
    //  TODO:  Write code to execute once work items are resubmitted.

 

        if (!_args.record() || !_args.caller())
        {
            throw error(Error::wrongUseOfFunction(funcName()));
        }

        WorkflowWorkItemTable workItem = _args.caller().getActiveWorkflowWorkItem();
        if (workItem)
        {
            try
            {
                WorkflowWorkItemActionDialog dialog = WorkflowWorkItemActionDialog::construct(
                    workItem,
                    WorkflowWorkItemActionType::Resubmit,
                    new MenuFunction(_args.menuItemName(),_args.menuItemType()));
                dialog.run();

                if (dialog.parmIsClosedOK())
                {
                    workItem = _args.caller().getActiveWorkflowWorkItem();
                    WorkflowWorkItemActionManager::dispatchWorkItemAction(
                        workItem,
                        dialog.parmWorkflowComment(),
                        dialog.parmTargetUser(),
                        WorkflowWorkItemActionType::Resubmit,
                        _args.menuItemName());

                    RecID recID = _args.record().RecId;
                    ttsbegin;
                    EmployeeTable::updateWorkFlowStatus(recID, WorkflowStatus::Submitted);
                    info("@AccountsPayable:VendBankAccountChangeProposalResubmit_Success");
                    ttscommit;
                }
            }
            catch(exception::Error)
            {
                info("@AccountsPayable:VednBankAccountChangeProposalResubmit_Error");
            }
        }

        // make sure changes in status are reflected on the caller
        if (FormDataUtil::isFormDataSource(_args.record()))
        {
            FormDataSource callerDS = FormDataUtil::getFormDatasource(_args.record());



            callerDS.reread();
            callerDS.refresh();
        }
        _args.caller().updateWorkflowControls();
  }

 

}


Step12:-Change labels for action menu items that have been created by systems

Note:- for cancel put Recall

Step 13:-In form design properties changes properties of Workflow

Configure workflow in front and assign required approvers.

Thankyou!!

Sunday, February 15, 2026

Generate a URL to navigate to D365fo for email sends

 using Microsoft.Dynamics.AX.Framework.Utilities;

using Microsoft.Dynamics.ApplicationPlatform.Environment;


public static str getRecordURL(str _menuItemName,MenuItemType _menuItemType,DataAreaId _dataArea,str _tableName,str _fieldName,str _fieldValue)
{
     str url;
     IApplicationEnvironment env = EnvironmentFactory::GetApplicationEnvironment();
     str currentUrl = env.Infrastructure.HostUrl;
     System.Uri currentHost = new System.Uri(currentUrl);

     UrlHelper.UrlGenerator generator = new UrlHelper.UrlGenerator();
     UrlHelper.RequestQueryParameterCollection requestQueryParameterCollection;


     generator.HostUrl = currentHost.GetLeftPart(System.UriPartial::Authority);
     generator.Company = _dataArea;
     generator.MenuItemName = _menuItemName;
     generator.MenuItemType = _menuItemType;
     generator.Partition = getCurrentPartition();
     generator.EncryptRequestQuery = true;

     requestQueryParameterCollection = generator.RequestQueryParameterCollection;
     requestQueryParameterCollection.UpdateOrAddEntry(_tableName, _fieldName,_fieldValue);

     System.Uri fullURI = generator.GenerateFullUrl();

     return fullURI.AbsoluteUri;
}

Thankyou

Send mail Through SMTP with Template

 In this blog we see how to send mail through SMTP by using template 

Step 1:-

  Code :-

  Map           templateTokens;

  templateTokens = new Map(Types::String, Types::String);     

   SysMailerSMTP           mailer = new SysMailerSMTP();

        try

        {

            switch (workflowContext.parmTableId())

            {

                case tablenum(LedgerJournaltable) :

                    SysMailerMessageBuilder builder = new SysMailerMessageBuilder(); 

                

                    SysEmailParameters      parameters = SysEmailParameters::find();

 

                    SysEmailMessageSystemTable  messageTable;

                    str envUrl = URLUtility::getUrl();

                    select messageTable

            where messageTable.EmailId == "Template name";


              

                    templateTokens.insert("envUrl",envUrl);

                    templateTokens.insert("Ledger journal table.JournalName",strFmt("%1",ledgerJournaltable.JournalName));  


                    if (parameters.SMTPRelayServerName)

                    {

                        mailer.smtpRelayServer(parameters.SMTPRelayServerName,


                                        parameters.SMTPPortNumber,


                                        parameters.SMTPUserName,


                                        SysEmailParameters::password(),


                                        parameters.SMTPUseNTLM,parameters.SMTPRequireSSL);

                    }

                    else

                    {

                        warning("ServerNotFound");

                    }

                    if (messageTable.EmailId)

                    {

                        builder.setFrom(parameters.SMTPUserName);

                        

                            builder.addTo(conPeek(con, i)).setSubject(strFmt("%1",messageTable.Subject + ledgerJournaltable.JournalNum)).setBody(SysEmailMessage::stringExpand(messageTable.Mail, SysEmailTable::htmlEncodeParameters(templateTokens)));

                         

                        SysMailerFactory::getNonInteractiveMailer().sendNonInteractive(builder.getMessage());

                        info('Mail successfully sent');

                    }

        }

     }

        catch (Exception::Error)

        {

            throw error("@SYS326780");

        }

    }

Step 2:-Configure template name in frontend 

Path:-

System administration > Setup > Email > System email templates

 


Thank you!!
Note:-Don't forget to configure SMTP details in front end and make sure done with Test connection

Monday, January 19, 2026

Debug In D365fo with Tier 2 environment database

 Today in this blog we will see how to debug in d365fo with Tier2 env's(uat,gc,preprod)

Step 1:-  Request JIT access 

If you have LCS access take JIT by providing IP of DEV Env  or else

Request from Admin We will get credentials in the format shown below


Servername/DBname

Username

Password

Step 2:- Turn off All Services like w3,worldwide,batches services 

Step 3:- Go to Folder from Filemanger K>AOSService\webroot as shown in image below and locate webconfig file

Step 5:- Copy the file and place it on desktop as back once debugging we need make back local DB

Step 6:- After that Right Click and open with latest visual studio(VS2022) as shown in below

Step 7:- After opening the file Press ctrlf and search AXDB and what ever credentials we got from LCS Replace with them as shown in below

Step 8:- After that for password ctrlf search for Password then change for Databaseacces.Sqlpwd as shown in below

Step 9:-  Save the changes and restart all  services and start debugging !!

Step 10:- Once you finish the process down the services and replace file and Turn on services to get back to normal


Thank You!!

Tuesday, December 9, 2025

Get ledger dimension description beside dimension in d365fo x++

 In this blog we learn how to Get ledger dimension description beside dimension in d365fo x++


Step1)write a display method in table and give in formdatasource

logic:-

       [ExtensionOf(tableStr(BudgetTransactionLine))]

internal final class DaxBudgetTransactionLine_Extension
{
        public display str descriptionLedger()
    {
        str                                 descriptionLedgerLoc = '';

     boolean                             first = true;
     DimensionAttributeLevelValueAllView view,viewloc;
     DimensionAttributeValue             dimValue;
     DimensionAttributeValueGroup        dimensionAttributeValueGroup;
     container                           con;

 

     select firstonly viewloc
         where viewloc.ValueCombinationRecId==this.LedgerDimension;


     while select view order by view.ValueOrdinal asc 
         where view.ValueCombinationRecId == this.LedgerDimension 
&& view.DimensionAttributeValueGroup==viewloc.DimensionAttributeValueGroup

     {
         dimValue = DimensionAttributeValue::find(view.AttributeValueRecId);

         if (!conFind(con,view.AttributeValueRecId))
         {
             con += [view.AttributeValueRecId];
             if (dimValue)
             {
                 if (!first)
                 {
                     descriptionLedgerLoc += " - ";
                 }

                 descriptionLedgerLoc += dimValue.getName();
                 first = false;
             }
         }
     }

     return descriptionLedgerLoc;
}

}

 

 

 Output:-

Wednesday, November 19, 2025

Filter Attachments Docuref when attachments are attached from standard and through custom

 [ExtensionOf(formDatasourceStr(DocuView,DocuRef))]

final class DaxDocuViewForm_Extension
{
    void initValue()
    {
        DocuRef      docuref;
        next initvalue();
        //Args args = element.args();
        //PurchTable     purchtable = args.record();
        if(element.args().menuitemname() == menuitemdisplaystr(Dax_VendorattachmentMenuitem))
        {
            docuref.DAX_NoYes = NoYes::Yes;
        }
        else
        {
            docuref.DAX_NoYes = NoYes::No;
        }
    }
}


and in Docuhistory:-

internal final class Dax_DocuHistoryTable
{

    [DataEventHandler(tableStr(DocuHistory), DataEventType::Inserted)]
    public static void DocuHistory_onInserted(Common sender, DataEventArgs e)
    {
        DocuHistory docuHistory = sender as DocuHistory;
        DocuHistory docuHistoryloc;
        DocuRef     docuRef;
        if(docuHistory.DocuValueDeleted==NoYes::No)
        {
            if (!docuHistory)
            return;

 

            select firstonly docuRef
                where docuRef.RecId == docuHistory.DocuRefRecId;

 

                if (docuRef)
                {
                    ttsbegin;
                    docuHistory.selectForUpdate(true);
                    docuHistory.Dax_IsCustomAttachment = docuRef.DAX_NoYes;
                    docuHistory.update();
                    ttscommit;
                }
        }
        else
        {
            if (!docuHistory)
            return;
            select firstonly docuHistoryloc
                where docuHistoryloc.DocuRefRecId==docuHistory.DocuRefRecId
&& docuHistoryloc.DocuValueDeleted==NoYes::No;
            ttsbegin;
            docuHistory.selectForUpdate(true);
            docuHistory.Dax_IsCustomAttachment =docuHistoryloc.Dax_IsCustomAttachment;
            docuHistory.update();
            ttscommit;

 

        }
    }

 

}

Tuesday, November 18, 2025

Highlight text of record based on condition in d365fo x++

 public void displayOption(Common _record, FormRowDisplayOption _options)

{

    WorkflowWorkItemTable workflowWorkItemTable;


    select workflowWorkItemTable

        where workflowWorkItemTable.Status==WorkflowWorkItemStatus::Pending

        && workflowWorkItemTable.UserId==curUserId()

        && workflowWorkItemTable.RefRecId==_record.RecId

        && workflowWorkItemTable.RefTableId==tableNum(BudgetTransactionLine);


    BudgetTransactionLine  budgetTransactionLine=_record;

    if(workflowWorkItemTable)

    {

        _options.textColor(WinAPI::RGB2int(0, 150, 0));

    }

    next displayOption(_record,_options);

}

Custom Workflow in D365fo

In this blog we see all about Workflows development in D365fo  Output as shown in below We will see in this blog first way Step1:-Create a b...