This code snippet allows you to change certain dimension values as needed and provides you with the updated ledger dimension.
Dynamics 365: Learn in Sharing
Thursday, April 25, 2024
Update only cost center from given ledger dimension using X++ in D365 F&O
Monday, April 15, 2024
Create a custom approval workflow in D365 F&O
This is a step by step procedure to create a custom approval workflow.
STEP 1 : Creating a workflow template
To start with creating a workflow, we need to first define it’s type/template.
Know more about workflow types/templates : https://technet.microsoft.com/en-us/library/dd362043.aspx
2. A wizard opens which needs Category, Document Class, Query as inputs to
create the template.
- Category
: To link the workflow to a particular module. You can link your workflow
with existing category or create a new one as per requirement.
https://msdn.microsoft.com/en-us/library/cc589698.aspx
- Query
: Containing the data source and fields required in workflow
configuration.
This query will be returned in the document class.
- Document
Class : Class to return workflow query and has parameter methods.
https://docs.microsoft.com/en-us/previous-versions/dynamicsax-2009/developer/cc592495(v%3dax.50)
3. Specifying template properties :
https://docs.microsoft.com/en-us/previous-versions/dynamicsax-2009/developer/cc594095(v%3dax.50)
4. Once a workflow template is created, following objects get
created :
Action menu items : CancelMenuItem, SubmitMenuItem
Classes : DocumentClass, WorkflowTypEventHandler, WorkflowTypeSubmitManager and
accordingly they are specified in the properties of the workflow type.
STEP 2
: Creating Workflow Approval
1. Creating new workflow approval :
https://msdn.microsoft.com/en-us/library/cc596847.aspx
2. After it’s creation, new objects get created :
Classes : WorkflowApprovalEventHandler
WorkflowResubmitActnMgr
Action Menu items : WorkflowApprovalResubmitMenuItem
WorkflowApprovalDelegateMenuItem
WorkflowApprovalApprove
WorkflowApprovalDeny
WorkflowApprovalRequestChange
WorkflowApprovalReject
3.
In the Outcomes node, there are 4 options : Approve, Deny,
Reject and RequestChange. They can be enables/disabled using properties as per
requirement. Corresponding to each outcome, a menu item is auto created once
the Workflow Approval is created. (As explained above)
1. Go to the setup in the module in which the workflow is required.
Taking example of Accounts Receivable module :
3. A wizard opens where you can configure your workflow by
simple drag and drop.
Steps explained : https://docs.microsoft.com/en-us/dynamics365/unified-operations/fin-and-ops/organization-administration/configure-approval-process-workflow
STEP 4
: Enabling workflow on UI
1.
Check the properties of the form in which WF is required. To follow this
tutorial,
WF Data Source - Specify accordingly
WF Enabled - No
WF Type
- Specify accordingly
If you are creating a form extension, you can use OnInitialized event handler
to specify the properties as follows :
sender.formRun().design().workflowDatasource(_WFDataSource_);
sender.formRun().design().workflowEnabled(false);
sender.formRun().design().workflowType(_WorkflowType_);
3. The drop dialog button highlighted in the above image links
to a Drop Dialog Form that will appear on clicking the Workflow button. That is
a new form that has to be created.
4. The WorkflowDropDialog form that has to be created is exactly same as the
PuchTableWorkflowDropDialog form. You can refer both the design as well as code
from this form to create your own.
STEP 5 : Functionality of Workflow
Now, the workflow framework is
ready and we can work on the functionality part i.e. what will happen on every
action related to the workflow.
I’ll explain this using an example to make things easier.
Example : Creating a workflow for Vendor Approval.
For all the below mentioned menu items :
SubmitMenuItem, WorkflowApproval, ResubmitMenuItem,
WorkflowApprovalDelegateMenuItem, WorkflowApprovalApprove,
WorkflowApprovalDeny, WorkflowApprovalRequestChange, WorkflowApprovalReject
Object is set as : VendorWorkflowActionManager
Functionality is defined using following created functions :
a). Main
Constructing workflowActionManager and
validating the arguments before taking the required action. Then it calls the
Run() function.
b).Run
Depending upon which menu item is clicked, it will
execute a function. It handles actions for : Submit, Approve, Reject and
RequestChange actions.
c). parmByPassDialog, parmWorkflowComment and parmCanceledAction
These methods are for setting value of CanceledAction and
BypassDialog. #DEFINE.BypassDialog('BypassDialog') This is the Macro used.
d). dialogOK
To opem a workflow Submit dialog for
confirming the submission of workflow.
e). submitToWorkflow
This is called when someone submits a
workflow. The workflow configuration and the vendor status is updated. For
updating vendor status, updateStatus function is called.
f). updateStatus
On submitting the workflow, the vendor
status is updated to InReview. Depending upon what action the workflow approver
takes, the status of Vendor is updated to Approved/ Rejected.
g). validateArgsObject
Validating passed arguments
Code snippet :
public class
VendorWorkflowActionManager
{
WorkflowComment workflowComment;
WorkflowVersionTable workflowVersionTable;
boolean bypassDialog;
boolean canceledAction;
#DEFINE.BypassDialog('BypassDialog')
public static void main(Args _args)
{
VendorWorkflowActionManager workflowActionManager =
VendorWorkflowActionManager::construct();
workflowActionManager.validateArgsObject(_args, funcName());
workflowActionManager.run(_args);
}
private void validateArgsObject(Args _args,
str _funcName)
{
if (!_args)
{
throw
error(Error::wrongUseOfFunction(_funcname));
}
}
public WorkflowComment
parmWorkflowComment(WorkflowComment _workflowComment = workflowComment)
{
workflowComment = _workflowComment;
return workflowComment;
}
public void run(Args _args)
{
WorkflowWorkItemActionManager
workflowWorkItemActionManager = new WorkflowWorkItemActionManager();
VendTable vendTable;
FormDataSource callerFormDataSource;
callerFormDataSource = _args.record().dataSource();
// Validate the args object isn't null.
this.validateArgsObject(_args,
funcName());
// Set the local caller properties.
Object caller = _args.caller();
Common callerRecord = _args.record();
vendTable =
callerRecord as VendTable;
str callerMenuItem =
_args.menuItemName();
str callerParm = _args.parm();
if (callerParm == #bypassDialog)
{
this.parmBypassDialog(true);
}
this.parmCanceledAction(false);
if (callerMenuItem ==
menuitemActionStr(VendorTemplateSubmit))
{
if(!this.submitToWorkflow(_args))
{
return;
}
}
else
{
workflowWorkItemActionManager.parmArgs(_args);
workflowWorkItemActionManager.parmCaller(caller);
workflowWorkItemActionManager.run();
this.parmCanceledAction(!workflowWorkItemActionManager.parmIsActionDialogClosedOK());
}
if (callerMenuItem ==
menuitemActionStr(VendorApprovalApprove) && !this.parmCanceledAction())
{
VendorWorkflowActionManager::updateStatus(vendTable,callerFormDataSource,VersioningDocumentState::Approved);
}
else if (callerMenuItem ==
menuitemActionStr(VendorApprovalResubmit) &&
!this.parmCanceledAction())
{
VendorWorkflowActionManager::updateStatus(vendTable,callerFormDataSource,VersioningDocumentState::InReview);
}
else if (callerMenuItem ==
menuitemActionStr(VendorApprovalReject) && !this.parmCanceledAction())
{
VendorWorkflowActionManager::updateStatus(vendTable,callerFormDataSource,VersioningDocumentState::Rejected);
}
}
public boolean
parmBypassDialog(boolean _bypassDialog = bypassDialog)
{
bypassDialog = _bypassDialog;
return bypassDialog;
}
public boolean
parmCanceledAction(boolean _canceledAction = canceledAction)
{
canceledAction = _canceledAction;
return canceledAction;
}
public boolean dialogOk(boolean ok = false)
{
WorkflowSubmitDialog workflowSubmitDialog;
if (!ok)
{
workflowSubmitDialog =
WorkflowSubmitDialog::construct(workflowVersionTable);
workflowSubmitDialog.run();
if (workflowSubmitDialog.parmIsClosedOK())
{
ok = true;
workflowComment = workflowSubmitDialog.parmWorkflowComment();
}
else
{
canceledAction = true;
}
}
return ok;
}
public boolean submitToWorkflow(Args _args)
{
VendTable vendTable;
FormDataSource callerFormDataSource;
callerFormDataSource = _args.record().dataSource();
this.validateArgsObject(_args,
funcName());
Common callerRecord = _args.record();
workflowVersionTable =
Workflow::findWorkflowConfigToActivateForType(workFlowTypeStr(VendorTemplate),
callerRecord.RecId,
callerRecord.TableId);
Debug::assert(workflowVersionTable.RecId != 0);
vendTable = callerRecord as VendTable;
Debug::assert(vendTable.RecId != 0);
canceledAction = false;
if (this.dialogOk(bypassDialog))
{
VendorWorkflowActionManager::updateStatus(vendTable,callerFormDataSource,VersioningDocumentState::InReview);
Workflow::activateFromWorkflowConfigurationId(workflowVersionTable.ConfigurationId,
callerRecord.RecId,
this.parmWorkflowComment(),
NoYes::No);
}
return true;
}
public static void
updateStatus(VendTable
_vendTable,
FormDataSource
_callerForm,
VersioningDocumentState _state)
{
if(_vendTable)
{
ttsbegin;
_vendTable.selectForUpdate(true);
_vendTable.VendorStatus = _state;
switch(_state)
{
case
VersioningDocumentState::Approved:
_vendTable.Blocked = CustVendorBlocked::No;
if(_callerForm &&
_callerForm.name() ==
formDataSourceStr(VendTable, VendTable))
{
_callerForm.allowEdit(true);
}
break;
case
VersioningDocumentState::InReview:
if(_callerForm &&
_callerForm.name() == formDataSourceStr(VendTable,
VendTable))
{
_callerForm.allowEdit(false);
_callerForm.allowDelete(false);
}
break;
case
VersioningDocumentState::Rejected:
if(_callerForm &&
_callerForm.name() == formDataSourceStr(VendTable, VendTable))
{
_callerForm.allowEdit(true);
}
break;
default:
break;
}
_vendTable.doUpdate();
ttscommit;
if(_callerForm &&
_callerForm.name() ==
formDataSourceStr(VendTable, VendTable))
{
_callerForm.refresh();
}
}
}
Just to add on, to add any additional functionalities, you can
use event handlers for different Approval Outcomes : Approve/Reject/Deny and
Request Change. Event handlers of Approve and Reject outcome can be used to
handle cases of auto approval and auto rejection respectively.
I hope you'll be able to create a custom WF using this blog easily. All the
best!