Кастомизация
10
Дек
0

«Корзина»

В CRM 2011 появилась такая замечательная функциональность как Аудит. С ее помощью Вы можете посмотреть, какие изменения произошли с Вашими записями. В том числе посмотреть удаленные записи и последние значения их полей. Но если Вам потребуется восстановить какие-либо из удаленных записей, то придется немного поработать ручкам. Но, как известно, лень – двигатель прогресса. Поэтому попробуем автоматизировать этот процесс.

Решение будет состоять из кастомной кнопки на общей Ленте, JS Веб-ресурса и WCF сервиса.

Начнем:

  • Создайте в VS 2010 проект WCF Application service с именем «recRec»:
    • Подключите к проекту следующие сборки:
      • microsoft.xrm.sdk
      • microsoft.crm.sdk.proxy
    • В файле IService1.cs поместите такой код:
      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Runtime.Serialization;
      using System.ServiceModel;
      using System.ServiceModel.Web;
      using System.Text;
      
      namespace recRec
      {
          // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService1" in both code and config file together.
          [ServiceContract(Namespace = "recRec")]
          public interface IService1
          {
              [OperationContract]
              string reccoveryRecord(Guid auditGUID);
          }
      }
      

      Здесь мы просто объявляем интерфейс для метода reccoveryRecord;

    • А в Service1.svc.cs поместите такой код:
      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Runtime.Serialization;
      using System.ServiceModel;
      using System.ServiceModel.Web;
      using System.ServiceModel.Description;
      using System.Text;
      using Microsoft.Xrm.Sdk;
      using Microsoft.Xrm.Sdk.Query;
      using Microsoft.Xrm.Sdk.Messages;
      using Microsoft.Xrm.Sdk.Client;
      using Microsoft.Crm.Sdk.Messages;
      using Microsoft.Xrm.Sdk.Metadata;
      
      namespace recRec
      {
          public class Service1 : IService1
          {
              public string reccoveryRecord(Guid auditGUID)
              {
                  try
                  {
      
                      // Подключаемся к CRM
                      ClientCredentials credentials = new ClientCredentials();
                      credentials.Windows.ClientCredential = new System.Net.NetworkCredential("Administrator", "1qaz@WSX", "D2011");
                      Uri uri = new Uri("http://crm2011:5555/superfirma/XRMServices/2011/Organization.svc");
                      OrganizationServiceProxy proxy = new OrganizationServiceProxy(uri, null, credentials, null);
                      proxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(new ProxyTypesBehavior());
                      IOrganizationService service = (IOrganizationService)proxy;
                      string retResult = "Запись восстановлена. ";
                      string delFields = null;
      
                      // Возвращаем данные записи аудита
                      RetrieveAuditDetailsRequest request = new RetrieveAuditDetailsRequest();
                      request.AuditId = auditGUID;
                      RetrieveAuditDetailsResponse response = (RetrieveAuditDetailsResponse)service.Execute(request);
                      Entity audit = response.AuditDetail.AuditRecord;
      
                      // Получаем GUID и имя удаленной записи
                      EntityReference entityAudited = (EntityReference)audit["objectid"];
                      Guid entityId = entityAudited.Id;
                      string entityLogicalName = entityAudited.LogicalName;
      
                      // Запрашиваем данные которые были удалены
                      RetrieveRecordChangeHistoryRequest changeRequest = new RetrieveRecordChangeHistoryRequest();
                      changeRequest.Target = new EntityReference(entityLogicalName, entityId);
                      RetrieveRecordChangeHistoryResponse changeResponse = (RetrieveRecordChangeHistoryResponse)service.Execute(changeRequest);
                      AuditDetailCollection details = changeResponse.AuditDetailCollection;
      
                      // Выполняем проверку что возвращенные данные аудита это запись удаления
                      for (int count = 0; count < details.Count; count++)
                      {
                          if (typeof(AttributeAuditDetail).Name == details[count].GetType().Name)
                          {
                              AttributeAuditDetail detail = details[count] as AttributeAuditDetail;
                              if (detail.NewValue == null && detail.OldValue != null)
                              {
                                  List<string> attrToClear = new List<string>();
                                  Entity entity = detail.OldValue;
      
                                  // Ищем лукапы в восстанавливаемом объекте
                                  foreach (KeyValuePair<String, Object> attribute in detail.OldValue.Attributes)
                                  {
                                      if (attribute.Value.ToString() == "Microsoft.Xrm.Sdk.EntityReference")
                                      {
                                          EntityReference lkp = (EntityReference)attribute.Value;
                                          try
                                          {
                                              // Проверяем что каждый лукап ссылается на существующую в CRM запись
                                              Entity resultEntity = service.Retrieve(lkp.LogicalName, lkp.Id, new ColumnSet(lkp.LogicalName + "id"));
                                          }
                                          catch
                                          {
                                              // Коллекционируем лукапы, ссылающиеся на не существующие записи
                                              attrToClear.Add(attribute.Key);
      
                                              // Запрашиваем из CRM Отображаемое имя не "корректных" лукапов
                                              RetrieveAttributeRequest attribReq = new RetrieveAttributeRequest();
                                              attribReq.EntityLogicalName = entityAudited.LogicalName;
                                              attribReq.LogicalName = attribute.Key;
                                              attribReq.RetrieveAsIfPublished = true;
                                              RetrieveAttributeResponse amRes = (RetrieveAttributeResponse)service.Execute(attribReq);
                                              AttributeMetadata attmetadata = amRes.AttributeMetadata;
      
                                              delFields += attmetadata.DisplayName.UserLocalizedLabel.Label + " ";
                                          }
                                      }
                                  }
      
                                  // Удаляем из восстанавливаемого объекта "устаревшие" записи
                                  foreach (string attr in attrToClear) entity.Attributes.Remove(attr);
      
                                  // Воссоздаем запись
                                  service.Create(entity);
      
                                  break;
                              }
                          }
                      }
      
                      // Добавляем к возвращаемому тексту имена удаленных лукапов
                      if (delFields != null)
                      {
                          retResult += "Следующие поля восстановить не удалось: " + delFields;
                      }
      
                      return retResult;
                  }
                  catch (Exception ex)
                  {
                      return ex.Message;
                  }
              }
          }
      }
      

      Этот сервис состоит из одного метода – reccoveryRecord – и он делает следующее:

      • Подключается к CRM;
      • Возвращает запись Аудита, переданную в метод reccoveryRecord сервиса;
      • Из нее выцепляем удаленную запись со своими полями;
      • Далее просматриваем поля удаленной записи на наличие лукапов. Если такое есть, то запрашиваем имеются ли еще в CRM записи, на которые они ссылаются. Если нет – удаляем лукапы из определения объекта;
      • При этом запрашиваем Отображаемые имена удаленных лукапов;
      • Создаем запись и формируем ответ, в который включаем имена удаленных лукапов (если таковые имеются).
    • А в Web.config поместите XML следующую разметку:
      <?xml version="1.0"?>
      <configuration>
        <system.web>
          <compilation debug="true" targetFramework="4.0" />
        </system.web>
        <system.serviceModel>
          <behaviors>
            <serviceBehaviors>
              <behavior>
                <serviceMetadata httpGetEnabled="true"/>
                <serviceDebug includeExceptionDetailInFaults="false"/>
              </behavior>
            </serviceBehaviors>
          </behaviors>
          <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
          <services>
            <service name="recRec.Service1">
              <endpoint address="" binding="basicHttpBinding" contract="recRec.IService1">
              </endpoint>
            </service>
          </services>
        </system.serviceModel>
       <system.webServer>
          <modules runAllManagedModulesForAllRequests="true"/>
        </system.webServer>
      </configuration>
      

      Ну, тут мы настраиваем WCF, чтобы он хотя бы притворялся что работает 🙂

    • Далее создайте в папке inetpub подпапку recoveryRec. Откройте менеджер IIS и создайте новый сайт recoveryRec, а в качестве папке укажите путь к только что созданной в папке inetpub. Также убедитесь, что пулу приложений нового сайта назначен 4 .Net Framework в интегрированном режиме.




  • Добавим кнопку на Ленту:
    • Создайте в CRM JS Веб-ресурс recovery.js с таким кодом:
      function recovery() {
          var ci = this.parent.parent.document.getElementById('contentIFrame').contentWindow;
          var va = ci.document.getElementById('ViewArea').contentWindow;
          var cg = va.document.getElementById('gridBodyTable');
          var rows = cg.childNodes[2].childNodes;
          for (var i = 0; i < rows.length; i++) {
              if (rows[i].firstChild.firstChild.checked) {
                  var nv = rows[i].childNodes[3].firstChild.firstChild.firstChild.nodeValue;
                  if (nv == 'Удалить') {
                      SOAP(rows[i].getAttribute('oid').replace("{", "").replace("}", ""));
                      va.document.getElementById("grid_refresh").parentNode.click();
                  }
              }
          }
      }
      
      function SOAP(guid) {
          var url = "http://crm2011:4444/Service1.svc";
          var body = "<s:Envelope xmlns:s='http://schemas.xmlsoap.org/soap/envelope/'>"
                          + "<s:Body>"
                              + "<reccoveryRecord xmlns='recRec'>"
                                  + "<auditGUID>" + guid + "</auditGUID>"
                              + "</reccoveryRecord>"
                          + "</s:Body>"
                      + "</s:Envelope>";
      
          var xmlHttp = new XMLHttpRequest();
          xmlHttp.open("POST", url, false);
          xmlHttp.setRequestHeader("Content-type", "text/xml; charset=utf-8");
          xmlHttp.setRequestHeader("soapAction", "recRec/IService1/reccoveryRecord");
          xmlHttp.onreadystatechange = function () {
              if (xmlHttp.readyState == 4) {
                  var xml = xmlHttp.responseXML;
                  var resp = xml.getElementsByTagName("reccoveryRecordResult");
                  alert(resp[0].text);
              }
          };
          xmlHttp.send(body);
      }
      

      Этот код будет вызываться с представления Общего представления аудита по кнопке. Код состоит из двух функций:

      • recovery – выискивает выделенные галками строки Представления. И если среди них найдены строки Удаления, то передает их GUID функции SOAP. После отработки функции SOAP обновляет Представление;
      • SOAP – выполняет вызов созданного выше WCF сервиса и передает ему GUID записи Аудита, также получает и выводит отчет.
    • Также добавьте в PNG Веб-ресурс с нужной иконкой (32х32) и именем recovery_32.png;
    • Создайте в CRM Решение, состоящее из Ленты приложения, и выгрузите его из CRM. Откройте файл customizations.xml в текстовом редакторе. Добавьте в раздел RibbonDiffXml такую разметку:
      <RibbonDiffXml>
        <CustomActions>
          <CustomAction Id="Recovery.CustomAction" Sequence="1" Location="Mscrm.BasicHomeTab.New.Controls._children">
            <CommandUIDefinition>
              <Button Id="Recovery.Button" Command="Recovery.javascriptCommand" Sequence="1" LabelText="Восстановить" ToolTipTitle="Восстановить" ToolTipDescription="Восстановить" TemplateAlias="o1" Image32by32="$webresource:new_recovery_32.png" />
            </CommandUIDefinition>
          </CustomAction>
        </CustomActions>
        <Templates>
          <RibbonTemplates Id="Mscrm.Templates"></RibbonTemplates>
        </Templates>
        <CommandDefinitions>
          <CommandDefinition Id="Recovery.javascriptCommand">
            <EnableRules>
              <EnableRule Id="Mscrm.Enabled" />
            </EnableRules>
            <DisplayRules />
            <Actions>
              <JavaScriptFunction Library="$webresource:new_recovery.js" FunctionName="recovery">
                <CrmParameter Value="SelectedControlSelectedItemIds" />
              </JavaScriptFunction>
            </Actions>
          </CommandDefinition>
        </CommandDefinitions>
        <RuleDefinitions>
          <TabDisplayRules />
          <DisplayRules />
          <EnableRules />
        </RuleDefinitions>
        <LocLabels />
      </RibbonDiffXml>
      

      Здесь у нас кнопочка, к которой привязан вызов JS-функции;

Импортируйте Решение обратно в CRM, перейдите в Сводное представление аудита и начинаем восстанавливать историю… 🙂



З.Ы. Помните, что кнопка работает только для строк аудита с событием «Удалить».

З.Ы.2 В некоторых случаях Вам не удастся восстановить запись. Это может быть связано с тем, что используемого значения пиклиста больше нет среди доступных, или был удален лукап, который для этого объекта был обязательным и т.д. В любом случае Вам вернется понятная ошибка.

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

*

code