Разработка
28
Дек
12

Вертикаль власти!

Сегодня рассмотрим парочку способов, как изобразить в CRM иерархические списки, основанные на тех или иных иерархических связях (которые в избытки присутствуют багодоря связям N:1, N:1 и N:N). И для затравки начнем с самого простого… с бесплатного аддона…

Addon Crm Tree View

Итак Crm Tree View это готовое и довольно гибко-настраиваемое решение для построения иерархических списков . Оно представляет собой ASPX, набор dll’ок и конфигурационный файл (не путать с web.config’ом от ASPX-страницы). А внешне выглядит как многоуровневый иерархический список, состоящий из записей нужного объекта, причем вложенные уровни могут соответствовать любому связанному объекту (плюс к этому имеется возможность создавать всплывающие подсказки).

Установка:

  • Скачайте Crm Tree View (32-битная и 64-битная) и разархивируйте куда-нибудь на жесткий диск;
  • Из папки bin, расположенной в архиве, скопируйте файл MSGPSD.TreeView.dll и поместите в папку bin сайта CRM;
  • Создайте в папке ISV (сайта CRM) подпапку HierarchicalView и скопируйте туда из архива папку App_Themes, файлы hierarchie.aspx и schema.xml;
  • Откройте на редактирование файл schema.xml и замените его код на следующий (возможно Вам понадобится подкорректировать некторые поля, например new_imageurl):
    <?xml version="1.0" encoding="utf-8" ?>
    <root xmlns="http://treeview.microsoft.com">
      <parent>
        <!--required-->
        <entity schemaname="account"></entity>
        <!--required-->
        <titlegroup>
          <attribute schemaname="name" />
          <attribute prefix=" [" schemaname="accountnumber" suffix="]" />
        </titlegroup>
        <popupgroup>
          <attribute schemaname="name" spancolumn="2" />
          <attribute schemaname="accountnumber" />
          <url schemaname="websiteurl" />
          <tel schemaname="telephone1" />
          <datetime schemaname="createdon" />
        </popupgroup>
        <child>
          <entity schemaname="contact"/>
          <titlegroup>
            <attribute schemaname="fullname"/>
          </titlegroup>
          <popupgroup>
            <image schemaname="new_imageurl"/>
            <attribute schemaname="fullname"/>
            <attribute schemaname="emailaddress1"/>
            <attribute schemaname="address1_line1"/>
            <attribute schemaname="address1_postalcode"/>
            <attribute schemaname="address1_city"/>
            <datetime schemaname="createdon"/>
          </popupgroup>
          <link>
            <parentattribute>accountid</parentattribute>
            <childattribute>parentcustomerid</childattribute>
          </link>
        </child>
      </parent>
    </root>
    
    

    Данный XML-код указывает программе, что основным объектом относительно которого строится иерархия является Бизнес-партнер, а дочерним к нему цепляется Контакт. Также для каждого объекта настроено всплывающее окно с контекстной информацией. Более подробно параметры настройки Вы сможете найти в Документации;

  • Экспортируйте из CRM SiteMap и добавьте в него такие строки (например, в Рабочую область):
    <SubArea Id="nav_treeview" Title="Иерархия" Icon="/_imgs/ico_16_oneToManyCustomRelationship_d.gif" Url="/ISV/HierarchicalView/hierarchie.aspx?file=schema.xml" Client="Web" PassParams="1" />
    

    Заметьте, что в строке URL указывается имя XML файла (схемы). Таким образом в разные ссылки можно подсовывать разные схемы;

  • Импортируйте SiteMap в CRM;
  • Идем тестировать (достаточно перейти по новой ссылке в области навигации 🙂 )…



Кастомная страничка

А теперь рассмотрим как нам реализовать нечто подобное «ручным» способом 🙂 Решение будет состоять из отдельной ASPX-странички и WEB-сайта. В самой же стрнички будет два основных элемента: иерархический список (подразделений) реализованный с помощью стандартного .Net контрола TreeView и iFrame’а, в котором будет отображаться форма редактирования записи Подразделения, по которой щелкнули мышкой в иерархическом списке.

Приступим:

  • Откройте Visual Studio и создайте новый ASP.NET сайт;
  • Подключите к проекту сборик SDK: microsoft.crm.sdk.dll и microsoft.crm.sdktypeproxy.dll;
  • Замените код на .aspx страничке таким:
    <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>
    
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Untitled Page</title>
        <script language="javascript" type="text/javascript">
            // Функция при изменении размеров окна браузера, меняет размер IFrame'ов по высоте доступной области
            function OnResizeComplete() {
                var h = document.body.clientHeight;
                var div = window.document.getElementById('right');
                div.style.height = h - 10;
            }
            // Функция вызываемая при загрузке
            function OnLoad() {
                OnResizeComplete(); // Сразу же подгоняем высоту iFrame'а по высоту окна браузера
    
                // Проверяем что Label1 содержит не дефолтное значение
                var id = document.getElementById('Label1').innerText;
                if (id != 'Label') { // ... если да, то
                    // Изменяем URL iFrame'а, используя GUID из Label1
                    var frame = document.getElementById('DetailFrame');
                    frame.src = "http://win-n22hj23d1b1/superfirma/biz/business/edit.aspx?id=" + id;
                }
            }
        </script>
        <style type="text/css">
            HTML, BODY
            {
    	        margin: 0;
    	        padding: 0;
    	        height:100%;
            }
            #left {
    	        width:34%;
    	        float:left;
    	        margin: 4px 0px 2px 0px;
            }
            #right {
    	        width:65%;
    	        height:100%;
    	        float:right;
    	        margin: 4px 4px 0px 0px;
    	        border: 1px solid #6699cc;
            }
            
            #DetailFrame 
            {
                margin: 0;
    	        padding: 0;
    	        width:100%;
    	        height:100%;   
            }
        </style>
    </head>
    <body onload="OnLoad();" onresize="OnResizeComplete();">
        <form id="form1" runat="server">
            <div id="left">
                <asp:TreeView ID="TreeView1" runat="server" ImageSet="XPFileExplorer" 
                    NodeIndent="15" onselectednodechanged="TreeView1_SelectedNodeChanged">
                    <ParentNodeStyle Font-Bold="False" />
                    <HoverNodeStyle Font-Underline="True" ForeColor="#6666AA" />
                    <SelectedNodeStyle BackColor="#B5B5B5" Font-Underline="False" 
                        HorizontalPadding="0px" VerticalPadding="0px" />
                    <NodeStyle Font-Names="Tahoma" Font-Size="8pt" ForeColor="Black" 
                        HorizontalPadding="2px" NodeSpacing="0px" VerticalPadding="2px" />
                </asp:TreeView>
                <asp:Label ID="Label1" runat="server" Text="Label" style="display:none"></asp:Label>
            </div>
            <div id="right">
                <iframe name="DetailArea" id="DetailFrame" frameborder="0" scrolling="no"></iframe>
    	    </div>
        </form>
    </body>
    </html>
    

    Что тут у нас есть:

    • Во-первых два DIV’а: в первом содержиться стандартный .Net контрол TreeView, а во-втором iFrame, содержимое которого динаимически меняется;
    • Также в первом DIV’е имеется стандартный .Net контрол Label, в который C# кодом помещает GUID подразделения, по которому пользователь щелкнул в древовидном списке;
    • Далее у нас идут две JavaScript-функции: первая изменяет размер iFrame в соответствии с высотой окна браузера, а вторая выцепляет из контрола Label GUID подразделения формирует из него строку URL (формы редактирования объекта Подразделение) и подсовывает ее в iFrame (измените в URL имя сервера на свое).
  • А на .aspx.cs повесьте такой C# код:
    using System;
    using System.Configuration;
    using System.Data;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.HtmlControls;
    using System.Web.UI.WebControls;
    
    using Microsoft.Crm.SdkTypeProxy;
    using Microsoft.Crm.Sdk.Query;
    using Microsoft.Crm.Sdk;
    
    public partial class _Default : System.Web.UI.Page 
    {
        public CrmService InitCrmService()
        {
            // Подключаемся к CRM-сервису
            CrmAuthenticationToken token = new CrmAuthenticationToken();
            token.AuthenticationType = 0; //Active Directory
            token.OrganizationName = "superfirma";
            CrmService crmService = new CrmService();
            crmService.Url = "http://win-n22hj23d1b1/mscrmservices/2007/crmservice.asmx";
            crmService.UseDefaultCredentials = true;
            crmService.PreAuthenticate = true;
            crmService.CrmAuthenticationTokenValue = token;
            return crmService;
        }
    
        // Эта функци просматриваеь дочернии подразделения определенной родительской оргацизации и добавляет их в дерево в качестве дочерних узлов
        public void GetChildBu(Guid myBUid, TreeNode myBUNode, RetrieveBusinessHierarchyBusinessUnitResponse allBu)
        {
            foreach (businessunit bu in allBu.BusinessEntityCollection.BusinessEntities)
            {
                if (bu.parentbusinessunitid != null) // Если нет родительской организации, то не рассматриваем
                {
                    Guid currentBUid = bu.businessunitid.Value;
                    Guid currentBUparentBUid = bu.parentbusinessunitid.Value;
    
                    if (currentBUparentBUid == myBUid)
                    {
                        // Добавляем каждый дочерний в иерархическое дерево
                        TreeNode myBUNode2 = new TreeNode(bu.name.ToString(), bu.businessunitid.Value.ToString());
                        myBUNode2.SelectAction = TreeNodeSelectAction.Select;
                        myBUNode2.ImageUrl = "http://win-n22hj23d1b1/_imgs/ico_16_10.gif";
                        myBUNode.ChildNodes.Add(myBUNode2);
    
                        // Сново просматриваем иерархию на предмет того, нет ли у текущего подразделения дочерних подразделений
                        GetChildBu(currentBUid, myBUNode2, allBu);
                    }
                }
            }
        }
    
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!this.IsPostBack)
            {
                // Инициализируем WebService...... 
                CrmService mySvc = InitCrmService();
    
                // Определяем поля которые следует вернуть (name, busineesunitid и parentbusinessunitid)
                ColumnSet cols = new ColumnSet(new string[] { "name", "businessunitid", "parentbusinessunitid" });
    
                // Возвращаем набор подразделений, начиная с головного (соответственно, Вам нужно подставить GUID своего головного подразделения)
                RetrieveBusinessHierarchyBusinessUnitRequest retrieve = new RetrieveBusinessHierarchyBusinessUnitRequest();
                retrieve.ColumnSet = cols;
                retrieve.EntityId = new Guid("208CC90C-4DA6-DF11-A623-000C29F6B309");
                RetrieveBusinessHierarchyBusinessUnitResponse retrieved = (RetrieveBusinessHierarchyBusinessUnitResponse)mySvc.Execute(retrieve);
    
                // Инициализируем головной узел иерархии
                TreeNode rootNode = null;
    
                // Просматриваем все возвращенные подразделения
                foreach (businessunit bu in retrieved.BusinessEntityCollection.BusinessEntities)
                {
                    // Если подразделение не имеет родительского подразделения, то это и есть головное
                    if (bu.parentbusinessunitid == null)
                    {
                        // Заносим головное подразделение в дерево иерархии
                        rootNode = new TreeNode(bu.name.ToString(), bu.businessunitid.Value.ToString());
                        rootNode.SelectAction = TreeNodeSelectAction.Select;
                        rootNode.ImageUrl = "http://win-n22hj23d1b1/_imgs/ico_16_10.gif";
                        TreeView1.Nodes.Add(rootNode);
                    }
                }
    
                // Просматриваем дочернии подразделения
                GetChildBu(retrieve.EntityId, rootNode, retrieved);
            }
        }
        protected void TreeView1_SelectedNodeChanged(object sender, EventArgs e)
        {
            this.Label1.Text = this.TreeView1.SelectedNode.Value;
        }
    }
    
    

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

    • Подключается к Web-сервису CRM под админом (соответственно, Вам нужно изменить параметры подключения на свои);
    • Вытаскиваем из CRM все подразделения начиная с головного и далее вниз по иерархии. Головное подразделение определяется по GUID’у, поэтому подставьте в код GUID головного своего подразделения – для этого откройте его форму, нажмите Ctrl + N и скопируйте GUID из строки URL нового окна браузер (заметьте, что головным с помощью данного запроса можно назначить любое другое подразделение и из CRM будет отобрано оно и все его дочерние подразделения);
    • Просматриваем возвращенные подраздеоения и находим то, у которого нет родительского подразделения — значит оно находится на самой верхушки CRM’ной иерархии подразделений. Добавляем его в основание иерархического списка;
    • Затем просматриваем возвращенный список повторно (с помощью функции GetChildBu) и находим подразделения дочерние для головного. Добавляем их в иерархический список. При этом для каждого дочернего подразделения повторно вызваем функцию GetChildBu, чтобы найти его дочерние подразделения из списка подразделений возвращенного из CRM и также добавить их в иерархическое дерево. И т.д.
    • Ну и одиноко стояий обработчик TreeView1_SelectedNodeChanged заносит в контрол Label GUID того подразделения иерархического списка, по которому был произведен клик;



  • Откройте IIS Manager и создайте новый сайт: с именем treeView, в качестве порта укажите 5555, а путь размещения c:\inetpub\treeView (предварительно создайте эту папку);
  • В представлении Feature View сайта treeView перейдите к разделу Authentication и и среди доступных опций включите только ASP.NET имперсанализацию и виндовую аутентификацию (остальне отключите);
  • Вернитесь в Visual Studio и перейдите Build — Publish Web Site — и опубликуйте сайт в папку c:\inetpub\treeView;
  • Далее выгрузите SiteMap из CRM и добавте в него такую строчку:
    <SubArea Id="treeView" Url="http://win-n22hj23d1b1:5555/default.aspx" Icon="/../_imgs/ico_16_10.gif" Title="Иерархия подразделений" />
    

    Импортируйте SiteMap обратно в CRM;

  • За сим все – можете открывать CRM и смотреть на кастомную страничку 🙂




Комментарии (12)
  • Alex 28.12.2010

    Привет.
    Кто разработчик этого Addon ?

  • Alex 28.12.2010

    Addon немного кривоват!
    При большом количестве бизнес-партнеров пытается отображать всех, и всех подчиненные записи.

  • Dmitry 28.12.2010

    Добрый день!
    большое спасибо за пример! очень актуально.
    А возможно ли кастомизировать левую панель следующим образом, например:
    мои задачи
    действия
    — представление 1
    — представление 2

    или для организаций:
    Организации
    — представление 1(например организации созданные за …)
    каким образом можно решить подобную задачу?

  • slivka_83 28.12.2010

    Ну очень просто 🙂
    создете нужную группу в SiteMap 🙂 вешаете в них SubArea с URLами нужных представлений и усе 🙂 А URL какого-либо представления Вы можете получить открыв его и Другие действия — Копировать ярлык.

  • Sergey 28.12.2010

    Добрый день.. у меня более простая задача назрела. Мне в форме Контакта, нужно отобразить в каком подразделении находится ответственный за него пользователь. Я не совсем понимаю как обратиться к owningbusinessunit из JavaScript на онлоаде формы.

  • slivka_83 28.12.2010

    А зачем к owningbusinessunit? почему просто не вытащить подразделение с карточки текущего юзвера? 🙂

  • Sergey 28.12.2010

    Вопрос в том, чтобы показать за каким подразделение закреплен БизнесПартнер. Текущий юзер может не быть ответственным за запись бизнеспартнера. Или я не так Вас понял?

  • Sergey 28.12.2010

    По сути, в карточках Контакта и БизнесПартнера мне нужно указать наименование подразделения в которое входит ответственный за эту запись пользователь.

  • slivka_83 28.12.2010

    Ну, вешаете на создание записи и на обновление поля ответственный скрипт, который вытаскивает из поля Ответственный его GUID, формируете SOAP запрос и запрашиваете Подразделение пользователя по его GUID’у 🙂

  • Михаил 28.12.2010

    День добрый,
    подскажите, иерархические списки можно создать в crm 2015 стандартными способами?

  • slivka_83 28.12.2010

    Добрый день.
    Зависит от того, что конкретно Вам нужно. Имеющаяся в CRM 2015 иерархия описана http://mmcrm.ru/?p=6240

*

code