Разработка
13
Фев
0

Динамический Маркетинговый список

Как и следует из названия, сегодня будем делать динамический Маркетинговый список. В текущей версии CRM Маркетинговый список можно пополнять только вручную (для этого существует несколько способов). Но люди ленивые… поэтому приходится думать… 🙂

Все решение состоит из нескольких JS-скриптов (главная задача которых – получить FetchXML-запрос из которого мы динамически будем формировать список актуальных записей Маркетингового списка) и кастомного бизнес-процесса, дополненного воркэраундом для создания повторяющегося Бизнес-процесса.

Приступим:

  1. Для начала создайте три новых поля для объекта Маркетинговый список:
    • Отображаемое имя: Представление
      Имя: new_savedquery
      Тип: picklist (не заполняйте его ничем)
    • Отображаемое имя: FetchXML
      Имя: new_fetchxml
      Тип: ntext
    • Отображаемое имя: Время обновления
      Имя: new_refreshdate
      Тип: datetime (с отображением времени)
  2. Создайте новую вкладку на форме Маркетингового списка и разместите на ней все три поля;
  3. На onLoad формы повесьте такой скрипт:
    if (crmForm.ObjectId) {
        var odj = crmForm.all.createdfromcode.DataValue;
        var authenticationHeader = GenerateAuthenticationHeader();
    
        // Prepare the SOAP message.
        var xml = "<?xml version='1.0' encoding='utf-8'?>" +
        "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'" +
        " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'" +
        " xmlns:xsd='http://www.w3.org/2001/XMLSchema'>" +
        authenticationHeader +
        "<soap:Body>" +
        "<RetrieveMultiple xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>" +
        "<query xmlns:q1='http://schemas.microsoft.com/crm/2006/Query'" +
        " xsi:type='q1:QueryExpression'>" +
        "<q1:EntityName>savedquery</q1:EntityName>" +
        "<q1:ColumnSet xsi:type='q1:ColumnSet'>" +
        "<q1:Attributes>" +
        "<q1:Attribute>name</q1:Attribute>" +
        "<q1:Attribute>savedqueryid</q1:Attribute>" +
        "</q1:Attributes>" +
        "</q1:ColumnSet>" +
        "<q1:Distinct>false</q1:Distinct>" +
        "<q1:Criteria>" +
        "<q1:FilterOperator>And</q1:FilterOperator>" +
        "<q1:Conditions>" +
        "<q1:Condition>" +
        "<q1:AttributeName>returnedtypecode</q1:AttributeName>" +
        "<q1:Operator>Equal</q1:Operator>" +
        "<q1:Values>" +
        "<q1:Value xsi:type='xsd:int'>" + odj + "</q1:Value>" +
        "</q1:Values>" +
        "</q1:Condition>" +
        "</q1:Conditions>" +
        "</q1:Criteria>" +
        "</query>" +
        "</RetrieveMultiple>" +
        "</soap:Body>" +
        "</soap:Envelope>";
        // Prepare the xmlHttpObject and send the request.
        var xHReq = new ActiveXObject("Msxml2.XMLHTTP");
        xHReq.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
        xHReq.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/RetrieveMultiple");
        xHReq.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
        xHReq.setRequestHeader("Content-Length", xml.length);
        xHReq.send(xml);
        // Capture the result.
        var resultXml = xHReq.responseXML;
        var results = resultXml.getElementsByTagName('BusinessEntity');
        var savedquery = crmForm.all.new_savedquery;
        for (i = 0; i < results.length; i++) {
            var name = results[i].selectSingleNode('./q1:name').nodeTypedValue;
            var savedqueryid = results[i].selectSingleNode('./q1:savedqueryid').nodeTypedValue;
            savedquery.AddOption(name, savedqueryid);
        }
    }
    

    Данный скрипт вытаскивает из CRM все представления того объекта, который выбран в текущем Маркетинговом списке и заполняет ими picklist «Представление».

    На onSave формы такой:

    crmForm.all.new_savedquery.DataValue = 0;
    

    Он очищает picklist «Представление» перед сохранение, на тот случай если пользователь выберет в нем какое-либо значение кроме первого (т.е пустого). В CRM нет никаких значений кроме пустого и попытка сохранить какое-либо значение кроме пустого приведет к ошибке.

    Ну, и на onChange поля «Представление»:

    if (crmForm.all.new_savedquery.DataValue) {
    
        // Prepare variables for a contact to retrieve.
        var savedqueryid = crmForm.all.new_savedquery.DataValue;
        var authenticationHeader = GenerateAuthenticationHeader();
    
        // Prepare the SOAP message.
        var xml = "<?xml version='1.0' encoding='utf-8'?>" +
        "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'" +
        " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'" +
        " xmlns:xsd='http://www.w3.org/2001/XMLSchema'>" +
        authenticationHeader +
        "<soap:Body>" +
        "<Retrieve xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>" +
        "<entityName>savedquery</entityName>" +
        "<id>" + savedqueryid + "</id>" +
        "<columnSet xmlns:q1='http://schemas.microsoft.com/crm/2006/Query' xsi:type='q1:ColumnSet'>" +
        "<q1:Attributes>" +
        "<q1:Attribute>fetchxml</q1:Attribute>" +
        "</q1:Attributes>" +
        "</columnSet>" +
        "</Retrieve>" +
        "</soap:Body>" +
        "</soap:Envelope>";
        // Prepare the xmlHttpObject and send the request.
        var xHReq = new ActiveXObject("Msxml2.XMLHTTP");
        xHReq.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
        xHReq.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/Retrieve");
        xHReq.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
        xHReq.setRequestHeader("Content-Length", xml.length);
        xHReq.send(xml);
        // Capture the result.
        var resultXml = xHReq.responseXML;
        var fetchxml = resultXml.selectSingleNode("//q1:fetchxml").nodeTypedValue;
        crmForm.all.new_fetchxml.DataValue = fetchxml;
    }
    

    Данный код самый интересный 🙂 Он считывает из пиклиста GUID выбранного в нем представления и отправляет SOAP запрос в CRM, с тем, чтобы вытащить Fetch-запрос представления. Данный Fetch-запрос будет использоваться для динамического формирования списка записей, которые необходимо добавить/удалить в/из Маркетингового списка.




  1. Откройте Visual Studio и создайте кастомный Бизнес-процесс (не забудьте подключить к нему сборки SDK и подписать его). В файл *.cs повесьте такой код:
    using System;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Collections;
    using System.Linq;
    using System.Workflow.ComponentModel.Compiler;
    using System.Workflow.ComponentModel.Serialization;
    using System.Workflow.ComponentModel;
    using System.Workflow.ComponentModel.Design;
    using System.Workflow.Runtime;
    using System.Workflow.Activities;
    using System.Workflow.Activities.Rules;
    using System.Xml;
    
    using Microsoft.Crm.Sdk;
    using Microsoft.Crm.SdkTypeProxy;
    using Microsoft.Crm.Workflow;
    using Microsoft.Crm.Workflow.Activities;
    using Microsoft.Crm.Sdk.Query;
    
    namespace ActivityLibrary2
    {
        // Задаем имя (отображаемое на форме) кастомного шага бизнес-процесса, и имя группы в которую он входит
        [CrmWorkflowActivity("Динамический список", "Мои бизнес-процессы")]
    
        public partial class Activity1 : SequenceActivity
        {
            public Activity1()
            {
                InitializeComponent();
            }
    
            // Определяем входной параметр, через который кастомному шагу будет передаваться FetchXml-запрос
            public static readonly DependencyProperty FetchXmlProperty = DependencyProperty.Register("FetchXml", typeof(string), typeof(Activity1));
    
            [CrmInput("FetchXml")]
            public string FetchXml
            {
                get { return (string)GetValue(FetchXmlProperty); }
                set { SetValue(FetchXmlProperty, value); }
            }
    
            protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
            {
                IContextService contextService = (IContextService)executionContext.GetService(typeof(IContextService));
                IWorkflowContext context = contextService.Context;
    
                // Если Бизнес-процесс выполняется не в отношении Маркетингового списка, то прекращаем выполнение
                if (context.PrimaryEntityName != EntityName.list.ToString())
                    return ActivityExecutionStatus.Closed;
    
                // Если FetchXml не задан, то прекращаем выполнение
                if (this.FetchXml == null || this.FetchXml.Length == 0)
                    return ActivityExecutionStatus.Closed;
    
                // Создаем CRM Service
                ICrmService crmService = context.CreateCrmService();
    
                // Возвращаем праметры (тип объекта к которому он привязан и статус блокировки) текущего Маркетингового листа
                ColumnSet cols = new ColumnSet(new string[] { "createdfromcode", "lockstatus" });
                list marketingList = (list)crmService.Retrieve(EntityName.list.ToString(), context.PrimaryEntityId, cols);
    
                // Если Маркетинговый список заблокирован, то прекращаем выполнение
                if (marketingList.lockstatus != null && marketingList.lockstatus.Value)
                    return ActivityExecutionStatus.Closed;
    
                // Выполняем Fetch-запрос
                string responseXml = crmService.Fetch(this.FetchXml);
                // Переводим ответ Fetch-запроса в XML структуру
                XmlDocument dom = new XmlDocument();
                dom.LoadXml(responseXml);
    
                // Определяем нужный узел XML структуры (который нам предстоит вытащить)
                string primaryattribute = string.Empty;
                switch (marketingList.createdfromcode.name.ToLower())
                {
                    case "контакт":
                        primaryattribute = "contactid";
                        break;
                    case "бизнес-партнер":
                        primaryattribute = "accountid";
                        break;
                    case "интерес":
                        primaryattribute = "leadid";
                        break;
                }
    
                // Отбираем возвращенные GUID'ы
                XmlNodeList nodeList = dom.SelectNodes("/resultset/result/" + primaryattribute);
    
                // Заносим список GUID'ов в массив GUID'ов
                Guid[] membersToEvaluate = new Guid[nodeList.Count];
                int i = 0;
                foreach (XmlNode node in nodeList)
                    membersToEvaluate[i++] = new Guid(node.InnerText);
    
                // Класс QualifyMemberListRequest проверяет, входят ли GUID'ы переданные ему, в Маркетинговый список.
                // Если нет, то он добавляет их Маркетинговый список.
                // Если же наоборот - в Маркетинговый список входят элементы, которых нет в переданном массиве, то он удаляет их из Маркетингового списка.
                QualifyMemberListRequest req = new QualifyMemberListRequest();
                req.ListId = context.PrimaryEntityId;
                req.MembersId = membersToEvaluate;
                req.OverrideorRemove = true;
    
                // Выполняем запрос
                QualifyMemberListResponse resp = (QualifyMemberListResponse)crmService.Execute(req);
                // Закрываем Бизнес-процесс
                return ActivityExecutionStatus.Closed;
            }
        }
    }
    

    Этот коде делает следующие вещи:

    • Объявляет входящую переменную (в которую необходимо передать FetchXML-запрос);
    • Получаем контекст выполнения Бизнес-процесса и вытаскиваем из него GUID маркетингового списка;
    • Подключимся к CRM сервису и вытаскиваем параметры текущего маркетингового списка: состояние блокировки и тип объекта, с которым работает Маркетинговый список;
    • Выполняем FetchXML-запрос переданный через входящую переменную и получаем список записей того объекта, с которым работает Маркетинговый список;
    • Из списка полученных записей формируем массив GUID’ов (этих же записей);
    • Выполняем запрос «QualifyMemberListResponse», в который передаем GUID текущего Маркетингового списка и масиив GUID’ов записей которые должны быть в него включены. Если каких то записей в нем нет, то он (запрос «QualifyMemberListResponse») их включит в Маркетинговый список. Если же наоборот – в Маркетинговом списке присутствуют лишние записи (которых нет в переданном массиве GUID’ов) то они будут исключены из него;
  2. Соберите Бизнес-процесс и зарегистрируйте в CRM с помощью Plugin Registration Tool;
  3. Создайте новый Бизнес-процесс для объекта Маркетинговый список. Для Области действия поставьте «Организация», а событием запуска – изменение поля «Время обновления». Добавьте к нему три шага:
    • Условие ожидания: таймаут наступления даты в поле «Время обновления» (последующие шаги будут дочерними для этого);
    • Следующим добавьте наш кастомный шаг Бизнес-процесса. В качестве параметра передайте ему поле FetchXML;
    • И третьим шагом добавьте обновление поля «Время обновления» — увеличьте его на один день (тем самым мы задаем период обновления Маркетингового списка в 1 день).
  4. Опубликуйте бизнес-процесс.



Тестирование

  1. Создайте новый Маркетинговый список, например, для объекта Контакт. Сохраните его;
  2. Перейдите на вкладку, на которой Вы расположили поля управления динамическим Маркетинговым списком и выберите какое-либо значение в пиклисте «Представление» (только убедитесь прежде что это представление что-нибудь возвращает). Тут же в поле «FetchXML» подтянется Fetch-запрос выбранного представления. Можете отредактировать его по своему усмотрению (тулзы Stunnware Tools, FetchXML Wizard и FetchXML Builder Вам в помощь 🙂 );
  3. Ну, и в поле «Время обновления» введите какую-либо будущую дату и время;
  4. Сохраните запись Маркетингового списка;
  5. Через некоторое время в списке Бизнес-процессов (на левой навигационной панели) появится новая запись, повествующая о том, что Бизнес-процесс работает. А еще через некоторое время (то которое указано в поле «Время обновления») к Маркетинговому списку добавятся новые участники 🙂 Также заметьте, что после этого в списке Бизнес-процессов (на левой навигационной панели Маркетингового списка) появится еще один Бизнес-процесс, который будет ожидать наступления даты и времени указанном в поле «Время обновления», которое было обновлено предыдущим Бизнес-процессом.



Комментарии (0)

*

code