Wednesday, May 19, 2021

Execute Dynamics 365 Workflow Using JavaScript on Click of Ribbon Button

 👉Trigger Dynamics 365 Workflow Using JavaScript On Click Of Ribbon Button

In Dynamics 365, you might come across a certain scenario where you want to execute a workflow using javascript code, and the same you may require to execute on ribbon button click.

This can be achieved through WebAPI calls.

Let’s start, 
We have an on-demand workflow that is created on Email activity and there is a ribbon button placed on the same email activity on the click of this button we set some of the field value on the email form as well as on the related entity records form.

1. Create an on-demand workflow and save and activate, as shown in the image below:



2. Get the workflow ID,  hardcode the Workflow Id, and Email Id (You can make the WebAPI call and get ID workflow ID and Email ID).  

Copy the Workflow Id from the URL: 


3.  Add email activity in a solution and open the solution from the ribbon workbench and add the button on the email form. Follow my previous blog to add a button on the ribbon - > Click here to add a button on the ribbon 


4 . Write the JavaScript Code and call it on the ribbon button  See how to call Javascript from the ribbon

Calling from ribbon workbench:



OnClickRibbonButton: function (primaryControl) {
        try {
            var formContext = primaryControl;
            var currentRecordGUID = formContext.data.entity.getId().replace(/[\])}[{(]/g, '');
            var workflowGUID = "e58deac6-fcdf-485a-a517-1ca020dcc86f";
            ExecuteWorkflow(formContext, workflowGUID, currentRecordGUID);
        }
        catch (error) { }

    },


5. Write a method to execute the workflow and call the below method on the button click method:

ExecuteWorkflow: function (context, workflowId, recordId) {
        var formContext = typeof context.getFormContext === "function" ? context.getFormContext() : context;
        var data = {
            "EntityId": recordId
        };
        var WorkflowId = workflowId;
        var req = new XMLHttpRequest();
        req.open("POST", formContext.context.getClientUrl() + "/api/data/v9.1/workflows(" + WorkflowId + ")/Microsoft.Dynamics.CRM.ExecuteWorkflow", true);
        req.setRequestHeader("OData-MaxVersion", "4.0");
        req.setRequestHeader("OData-Version", "4.0");
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.onreadystatechange = function () {
            if (this.readyState === 4) {
                req.onreadystatechange = null;
                if (this.status === 200 || this.status === 204) { // Asynchronous || Synchronous
                    formContext.ui.refreshRibbon(); 
                }
                else {
                    Xrm.Utility.alertDialog(this.statusText);
                }
            }
        };
        req.send(JSON.stringify(data));
    },


Click here to learn more: Javascript Common Function    Home
-------------------------------------------------------------------------------------------

Thanks !!!
Happy Leaning ...
Keep CRMing....

Tuesday, May 18, 2021

Use different types of Input and Output arguments for custom workflow activity

How to use different types of Input and Output arguments for custom workflow activity 👈

There are certain situations where we require different types of Input arguments to pass to Custom Workflow activity and in some cases, we might require to get some of the code execution results as Output arguments from a custom workflow activity.

In order to accomplish this, we need Syntax to implement Input and Output arguments for different types of fields.

Here I considered an entity called Patient which is having different types of fields and those are given below

1. Patient Name: String
2. Date Of Birth: Date and Time
3. Hospital: Lookup (to an entity called Hospital)
4. Patient Status: Option Set
5. Hospitalization Required: Two Option Set
6. Patient Age: Whole Number
7. Consultation Fee: Decimal Number
8. Estimated Amount: Floating Point Number
9. Treatment Cost: Currency
10. Patient Id: String


Types of arguments

Input: Entity field value can be passed to a Custom workflow activity
Output: Execution result (if any) can be passed as output from Custom workflow activity
Input/Output: It can be used as both Input and Output

Below is the Custom Workflow Activity code with syntax to declare Input and Output arguments.👇

using System;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Workflow;
using System.Activities;
 
namespace WorkflowEmailAttachment
{
    public class InputOutputParameters : CodeActivity
    {
        //Data Type : String
        [Input("patientname")]
        [RequiredArgument]
        public InArgument<string> PatientName { get; set; }
 
        //Data Type : Date and Time
        [Input("dateofbirth")]
        [RequiredArgument]
        public InArgument<DateTime> DateOfBirth { get; set; }
 
        //Data Type : Lookup
        [Input("hospital")]
        [ReferenceTarget("new_hospital")]
        [RequiredArgument]
        public InArgument<EntityReference> Hospital { get; set; }
 
        //Data Type : Option Set
        [Input("patientstatus")]
        [RequiredArgument]
        [AttributeTarget("new_patient", "statuscode")]
        public InArgument<OptionSetValue> PatientStatus { get; set; }
 
        //Data Type : Two Option Set
        [Input("hospitalizationrequired")]
        [RequiredArgument]
        public InArgument<bool> HospitalizationRequired { get; set; }
 
        //Data Type : Whole Number
        [Input("patientage")]
        [RequiredArgument]
        public InArgument<int> PatientAge { get; set; }
 
        //Data Type : Decimal Number
        [Input("consultationfee")]
        [RequiredArgument]
        public InArgument<decimal> ConsultationFee { get; set; }
 
        //Data Type : Floating Point Number
        [Input("estimatedamount")]
        [RequiredArgument]
        public InArgument<decimal> EstimatedAmount { get; set; }
 
        //Data Type : Currency
        [Input("treatmentcost")]
        [RequiredArgument]
        public InArgument<Money> TreatmentCost { get; set; }
 
        //Data Type : String
        [Output("showpatientdetails")]
        public OutArgument<string> ShowPatientDetails { get; set; }
 
 
        //Data Type : String (Can be used as Input/Output)
        [Input("patientinput")]
        [Output("patientoutput")]
        [RequiredArgument]
        public InOutArgument<string> PatientInOut { get; set; }
 
 
        protected override void Execute(CodeActivityContext executionContext)
        {
            try
            {
                // Get workflow context
                var tracingService = executionContext.GetExtension<ITracingService>();
                var context = executionContext.GetExtension<IWorkflowContext>();
                var serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
                var service = serviceFactory.CreateOrganizationService(context.UserId);
 
                tracingService.Trace("Patient Details using input and output parameters Workflow Started.");
                var patientName = PatientName.Get<string>(executionContext);
                var dateOfBirth = DateOfBirth.Get<DateTime>(executionContext);
                var hospital = Hospital.Get<EntityReference>(executionContext)?.Name;
                var patientStatus = PatientStatus.Get<OptionSetValue>(executionContext).Value;
                var hospitalizationRequired = HospitalizationRequired.Get<Boolean>(executionContext);
                var patientAge = PatientAge.Get<Decimal>(executionContext);
                var consultationFee = ConsultationFee.Get<Decimal>(executionContext);
                var estimatedAmount = EstimatedAmount.Get<float>(executionContext);
                var treatmentCost = TreatmentCost.Get<Money>(executionContext).Value;
                var patientId = PatientInOut.Get<string>(executionContext);
                tracingService.Trace($"Patient Name : {patientName}, Date Of Birth : {dateOfBirth}, Hospital : {hospital}, Patient Status : {patientStatus}, Hospitalization Required: {hospitalizationRequired}, Patient Age: {patientAge}, Consultation Fee : {consultationFee}, Estimated Amount : {estimatedAmount}, Treatment Cost : {treatmentCost}, Patient ID : {patientId}");
                var patientDetails = $"Patient Name : {patientName}, Date Of Birth : {dateOfBirth}, Hospital : {hospital}, Patient Status : {patientStatus}, Hospitalization Required: {hospitalizationRequired}, Patient Age: {patientAge}, Consultation Fee : {consultationFee}, Estimated Amount : {estimatedAmount}, Treatment Cost : {treatmentCost}, Patient ID : {patientId}";
                PatientInOut.Set(executionContext, PatientInOut.ToString());
                ShowPatientDetails.Set(executionContext, patientDetails);
                tracingService.Trace("Patient Details using input and output parameters Workflow Ended.");
            }
            catch (Exception ex)
            {
                throw new InvalidPluginExecutionException(ex.Message);
            }
        }
    }
}

 ------------------------------------------------------------------------------------------------------

The below image will show you the representation of the Input arguments once you successfully build and deploy the above custom workflow code activity.



Click here to learn more about workflow activity.

Thank you!!
Happy CRMing..

Keep Learning....!!!!

Monday, May 17, 2021

Dynamics 365 workflow to send an email with notes attachment

😇 Dynamics 365 workflow to send an email with notes attachment👆

A business has a requirement to send an email from Dynamics 365 with the attached document which is present in the timeline section. That document basically needed to come from a notes attachment on a task record. Refer to WorkflowActivity 

As you might have already tried existing OOB workflow functionality to achieve this. I also did the same tried multiple ways with OOB functionality but didn't find a way to achieve it.

Note: We can’t access an attachment from Notes in an OOB workflow so we need to write a custom workflow where we need to get an entity record attachment from an annotation entity and need to create an activitymimeattachment record and need to associate it with the email created. While creating the workflow we need to use a create step instead of sending the email so that we can refer to the email record id.

I also searched many blogs and came across an MS Community blog and Luckily there was one solution which was exactly matching with my requirement, to know more Click here

Here I'm sharing my own implementation. Below is the procedure to implement to achieve our requirement through custom workflow activity:

  1. Develop a custom workflow activity assembly to get an attachment.
  2. The code targets task activity in my case. But here Its a generic code, which can be used on any entity.
  3. Get all the note attachments from the timeline section.
  4. While creating the workflow we need to use a create step instead of sending the email so that we can refer to the email record id.
Create a custom workflow activity. Create a class library project and add all the required assemblies to your project. Let’s rename our class file to SendEmailWithAttachement and use the following code:

using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Workflow;
using System;
using System.Activities;
 
namespace WorkflowEmailAttachment
{
    public class SendEmailWithAttachement : CodeActivity
    {
        [Input("SourceEmail")]
        [ReferenceTarget("email")]
        public InArgument<EntityReference> SourceEmail { getset; }
 
        protected override void Execute(CodeActivityContext executionContext)
        {
            // Get workflow context
            var tracingService = executionContext.GetExtension<ITracingService>();
            var context = executionContext.GetExtension<IWorkflowContext>();
            var serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
            var service = serviceFactory.CreateOrganizationService(context.UserId);
 
            try
            {
//context.PrimaryEntityId means Task ID
                AddAttachmentToEmailRecord(service, context.PrimaryEntityId, SourceEmail.Get<EntityReference>(executionContext));
            }
            catch (Exception e)
            {
                throw new InvalidPluginExecutionException(e.Message);
            }
        }
//sourceId = taskId
private void AddAttachmentToEmailRecord(IOrganizationService service, Guid sourceId, EntityReference sourceEmailID)
        {
//Create Email Object
            Entity resultEntity = service.Retrieve("email", sourceEmailID.Id, new ColumnSet(true));
 
            QueryExpression queryNotes = new QueryExpression("annotation");
            queryNotes.ColumnSet = new ColumnSet(new string[] { "subject""mimetype""filename""documentbody" });
            queryNotes.Criteria = new FilterExpression();
            queryNotes.Criteria.FilterOperator = LogicalOperator.And;
            queryNotes.Criteria.AddCondition(new ConditionExpression("objectid", ConditionOperator.Equal, sourceId));
//Retrieve all the notes and store it in entity collection
            EntityCollection mimeCollection = service.RetrieveMultiple(queryNotes);
 
            foreach (var attachment in mimeCollection.Entities)
            {
//Create Email Attachment
                Entity emailAttachment = new Entity("activitymimeattachment");
                if (attachment.Contains("subject")) emailAttachment["subject"] = attachment.GetAttributeValue<string>("subject");
                if (attachment.Contains("filename")) emailAttachment["filename"] = attachment.GetAttributeValue<string>("filename");
                if (attachment.Contains("documentbody")) emailAttachment["body"] = attachment.GetAttributeValue<string>("documentbody");
if (attachment.Contains("mimetype")) emailAttachment["mimetype"] = attachment.GetAttributeValue<string>("mimetype");
                emailAttachment["objectid"] = new EntityReference("email", resultEntity.Id);
                emailAttachment["objecttypecode"] = "email";
                service.Create(emailAttachment);
            }
 //Send Email
            SendEmailRequest sendRequest = new SendEmailRequest();
            sendRequest.EmailId = resultEntity.Id;
            sendRequest.TrackingToken = "";
            sendRequest.IssueSend = true;
            SendEmailResponse res = (SendEmailResponse)service.Execute(sendRequest);
        }
    }

}

----------------------------------------------------------------------------------------------------------------------------------------

Build and register your assembly using the plug-in registration tool. Now we need to create our workflow to use our custom assembly step.

Navigate to Settings->Process to create a new process. Select Task in entity drop-down and workflow in the category.

Follow the below steps to configure your workflow

  • Add a step to create an email record that can be used in the custom step
  • Add our custom workflow steps from Add Step.
Create workflow a process as shown in the below image:














Click on Add Step -> Click on Create Records -> Select Email -> Set Properties:


Click on Add steps-> Select workflow which you registered in plugin registration tool (popped up here) -> Click on Set Properties -> Add details as shown:




Save & Activate the workflow -> The workflow looks like as shown below: