Кастомизация
02
Июл
2

Аля CRM Online Dashboard

В одном из прошлогодних обновлений MS CRM Online была включена панель управления, позволяющая просматривать на одной странице представление и тут же строить отчет по нему. Решение интересное и полезное 🙂 Поэтому «перенесем» его на локальную версию MS CRM…

Решение будет состоять из следующих элементов:

  • HTML-странички, на которой будут размещаться два iFrmae’а, в которых, в свою очередь, будут отображаться отчет написанный на Reporting Services и представление CRM. В это представление будет добавлена кнопка (с именем «График»), при нажатии на которую, будет формироваться отчет (состоящий из графиков), но только по выделенным в представлении записям;
  • Таблички в БД, содержащей два поля: GUID Бизнес-партнеров (в данном примере именно и их представлением мы будем работать) и GUID юзвера, который этих Бизнес-партнеров выбрал на своем компьютере (или не на своем 🙂 но важно дифференцировать наборы выделенных Бизнес-партнеров для разных пользователей) и нажал кнопку «График»;
  • ASPX-приложения, которое получает из HTML-странички (с помощью JavaScript’а) методом POST GUID’ы выделенных Бизнес-партнеров и записывает их в БД вместе с GUID’ом пользователя нажавшего кнопку (чтобы при формировании отчета система знала, какие записи Бизнес-партнеров необходимо отобрать для данного конкретного юзвера);
  • HTML-странички-заглушки: представляет собой просту HTML-страничку содержащую надпись «Отчет не сформирован». Она будет подставляться в iFrame’е отчета до его формирования и в случаи если при записи данных в БД произошла ошибка;

«Поехали» 🙂

Настройка БД

  • Создайте новую табличку в БД ORG_MSCRM, выполнив в отношении нее такой SQL скрипт:
    CREATE TABLE [dbo].[DashboardOnline](
    	[accguid] [varchar](50) NULL,
    	[userguid] [varchar](50) NULL
    ) ON [PRIMARY]
    
  • Добавьте на SQL сервер нового юзвера (или используйте уже существующего – если он у Вас есть), под которым ASPX-приложение будет коннекститься к БД. Для этого в том же SQL Management Studio перейдите Security — щелкните правой кнопкой мыши по Login — New Login — задйте логин и пароль для нового юзвера — перйдите к разделу Server Roles и поставьте галку sysadmin (это самые широкие права в SQL среде, поэтому на промышленном сервере необходимо выдавать их более целеноправленнj);
  • Также убедитесь что Ваш SQL сервер поддерживает скульную аутентификацию: правой кнопкой по корневому элементу сервера — Properties — раздел Security — галка Sql Server and Windows Authentication mode.



HTML и ASPX

  • Создайте в Visual Studio новый проект сайта ASP.NET;
  • Добавьте к проекту HTML страницу: щелкните правой кнопкой мыши по названию проекта Add New Item — выбрать HTML документ и задать для него имя DashboardOnline.htm;
  • Поместите на DashboardOnline.htm такой код:
    <html>
    <head>
        <title>Панель управления</title>
        <style type="text/css">
            body
            {
                margin: 0;
                padding: 0;
                height: 100%;
                width: 100%;
            }
            iframe
            {
                width: 100%;
            }
        </style>
     
        <script type="text/javascript"> 
            function OnLd() 
            {
                // Вытаскиваем из CRM GUID текущего пользователя     
                var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
                xmlhttp.open("POST", "/mscrmservices/2007/crmservice.asmx", false);
                xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
                xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/Execute");
                var soapBody = "<soap:Body>" +
                "<Execute xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>" +
                "<Request xsi:type='WhoAmIRequest' />" +
                "</Execute></soap:Body>";
                var soapXml = "<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'>";
                soapXml += document.getElementById("View").contentWindow.GenerateAuthenticationHeader(); // Получаем доступ к функции GenerateAuthenticationHeader() находящуюся во фрейме View
                soapXml += soapBody;
                soapXml += "</soap:Envelope>";
                xmlhttp.send(soapXml);
                xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
                xmlDoc.async = false;
                xmlDoc.loadXML(xmlhttp.responseXML.xml);
    
                userid = xmlDoc.getElementsByTagName("UserId")[0].childNodes[0].nodeValue;
    
                // Задаем иям отчета
                reportName = "Report1";
                // Создаем ссылку на iFrame с отчетом
                reportIFrame = document.getElementById('Report');
            }
            
            function OnResizeComplete() 
            { 
                var fr = window.document.getElementById('View'); 
                fr.style.height = document.body.clientHeight - 230;
            }
            
            function AddButton()
            {
                var frameDoc = document.getElementById("View").contentWindow.document;
        
                  // Создаем разделитель
                  var Spacer = '';
                  Spacer += '<li class="ms-crm-Menu-Spacer" tabIndex="-1">';
                  Spacer += '&nbsp';
                  Spacer += '<img style="clip: rect(0px 4px 17px 0px); background-image: url(/_imgs/imagestrips/control_imgs_1.gif); width: 4px; background-position-y: -55px; height: 17px" alt="" src="/_imgs/imagestrips/transparent_spacer.gif">';
                  Spacer += '</li>';
     
                  // Создаем кнопку
                  var html = '';
                  html += Spacer; // Добавляем перед кнопкой разделитель
                  html += '<li id="Li1" class="ms-crm-Menu" title="Сформировать график" tabindex="-1" onclick="window.execScript(action)" action="parent.SendRequest(document.all[\'crmGrid\'].InnerGrid.SelectedRecords);"><span class="ms-crm-Menu-Label"><a class="ms-crm-Menu-Label" tabindex="-1" onclick="return false;" href="javascript:onclick();" target="_self">';
                  html += '<img class="ms-crm-Menu-ButtonFirst" tabindex="-1" alt="Сформировать график" src="/_imgs/ico_18_salesOverview.gif">';
                  html += '<span class="ms-crm-MenuItem-TextRTL" tabindex="0">График</span></a></span>';
                  html += '</li>';
     
                  // Добавляем кнопку в DOM-структуру
                  var toolbar = frameDoc.all.mnuBar1.rows(0).cells(0).childNodes[0];
                  toolbar.insertAdjacentHTML("beforeEnd", html);
            }
            
            function SendRequest(SelectedRecords)
            {
                // Просматриваем полученные GUID'ы и заносим их в массив
                var selectedItems = new Array(SelectedRecords.length);
                for (var i=0; i < SelectedRecords.length; i++)
                    selectedItems[i] = SelectedRecords[i][0];
    
                // Отправляем асинхронный запрос в страницу Default.aspx и методом POST передаем ей GUID'ы Бизнес-партнеров и текущего юзвера
                var objXMLHTTP = new ActiveXObject('Microsoft.XMLHTTP');
                objXMLHTTP.Open('POST','Default.aspx',false);
                objXMLHTTP.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
                objXMLHTTP.Send('accids=' + selectedItems.toString() + "&userid=" + userid); // Передаем GUID'ы Бизнес-партнеров и GUID текущего пользователя методом POST
     
                // Получаем ответ
                var response = objXMLHTTP.ResponseText;
    
                // Если ответ положительный, то...
                if (response == "Готово") {
                    // Обновляем отчет
                    reportIFrame.src = "http://win-n22hj23d1b1/ReportServer/Pages/ReportViewer.aspx?%2f" + reportName + "&rs:Command=Render&rc:Toolbar=false&rs:ClearSession=true&userid=" + userid;
                } else { // Иначе ставим «заглушку» в iFrame с отчетом
                    reportIFrame.src = "blank.htm";
                }
            }
        </script>
     
    </head>
    <body onload="OnResizeComplete();AddButton();OnLd();" onresize="OnResizeComplete();">
        <iframe id="Report" src="blank.htm" height="230px" frameborder="0" scrolling="no" />
        <iframe id="View" src="http://win-n22hj23d1b1/superfirma/_root/homepage.aspx?etc=1&viewid=00000000-0000-0000-00AA-000010001002" scrolling="no" frameborder="0" />
    </body>
    </html>
    

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

    • Как было ужа сказано здесь два iFrame’а. В верхнем будет отображаться отчет (имеет статичный размер), а в нижнем представление CRM (динамически заполняет всю оставшуюся область страницы);
    • Изначально в верхнем фрейме отображается «страница-заглушка» (ее мы создадим в следующем пункте). А в нижнем представление CRM, которое Вам нужно заменить на собственное. Для этого откройте какое либо представление в CRM и перейдите Другие действия — Копировать ярлык — Из текущего представления — ссылка на представление скопируется в буфер обмена. Ззатем вставьте эту ссылку в атрибут SRC нижнего фрейма;
    • При загрузке страницы срабатывают несколько функции:
      • OnResizeComplete: динамически изменяет размер нижнего фрейма в зависимости от размеров окна IE (также срабатывает при изменении размеров окна);
      • AddButton: динамически добавляет в представление CRM (в нижний фрейм) кнопку, по нажати на которую формируется отчет;
      • OnLd: делает три вещи — получает GUID текущегу юзвера, задает имя отчета который будет отображаться в верхнем фрейме и создает ссылку на этот фрейм.
    • Что происходит по нажатию на добавленную в представление кнопку:
      • Происходит считывание GUID’ов Бизнес-партнеров выбранный в представлении и передача их в функцию SendRequest;
      • Функция SendRequest упаковывает эти GUID’ы + GUID текущего пользователя и передает методом POST в страницу Default.aspx;
      • Если Default.aspx вернет положительный ответ (т.е. записи успешно помещены в БД), то далее обновляется iFrame отчета (в строчке подключения Вам необходимо изменить имя сервера отчетов), с тем чтобы отобразить актуальные данные. При этом в отчет в троке запроса передается GUID текущего пользователя, чтобы отчет отобрал только его данные;
  • Добавьте к проекту еще одну HTML страницу с именем blank.htm и поместите в нее следующий код:
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
    <title>Blank</title>
        <style type="text/css">
            html,body {
                height:100%;
                margin:0px;
                padding:0px;
                font: 52px Tahoma;
                font-weight: bold;
                color: #d9dceb;
                background: #e7e8f0;
            }
        </style>
    </head>
    <body>
        <table width="100%" height="100%">
            <tr>
                <td align="center">Отчет не сформирован</td>
            </tr>
        </table>
    </body>
    </html>
    

    Это просто заглушка для iFrame’а, когда в нем не отображается отчет;

  • Откройте Default.aspx и удалите весь код, кроме первой строчки:
    <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>
    
  • Перейдите на Default.aspx.cs и замените его код на следующий:
    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 System.Web.UI.WebControls.WebParts;
    using System.Data.SqlClient;
    
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            try
            {
                // Подключаемся к SQL серверу
                SqlConnection myConnection = new SqlConnection(
                    "user id=crmuser;" +
                    "password=1qaz@WSX;" +
                    "server=win-n22hj23d1b1;" +
                    "database=superfirma_MSCRM; " +
                    "Trusted_Connection=yes;" +
                    "connection timeout=30"
                );
                myConnection.Open();
    
                // Формируем и выполняем SQД запрос по удаленияю из таблички DashboardOnline записи текущего пользователя
                SqlCommand myCommand = new SqlCommand("DELETE FROM DashboardOnline WHERE userguid = '" + Request.Form["userid"] + "'", myConnection);
                myCommand.ExecuteNonQuery();
    
                // Формируем (и выполняем) строки SQL запроса INSERT, с тем чтобы доавить в такбличку DashboardOnline новые GUID'ы, выбранных (конкретным юзвером) Бизнес-партнеров
                string insert = null;
                string[] accids = Request.Form["accids"].Split(',');
    
                foreach (string accid in accids)
                {
                    insert += "INSERT INTO DashboardOnline (accguid, userguid) Values ('" + accid.Replace("{", "").Replace("}", "") + "', '" + Request.Form["userid"] + "');";
                }
    
                SqlCommand myCommand2 = new SqlCommand(insert, myConnection);
                myCommand2.ExecuteNonQuery();
    
                myConnection.Close();
                
                // Возвращаем положительный ответ
                Response.Clear();
                Response.Write("Готово");
            }
            catch
            {
                // В случаи какой-либо ошибки возвращаем отрицательный ответ
                Response.Clear();
                Response.Write("Ошибка");
            }
        }
    }
    

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

    • Устанавливает подключение к SQL серверу (измените параметры подключения в соответствии с Вашими);
    • Удаляем из БД строки «устаревших» GUID’ов Бизнес-партнеров (но только строки текущего юзвера);
    • Добавляем в БД новый GUID’ы идентифицировав из с текущем пользователем
    • Возвращаем ответ страничке DashboardOnline.htm;
  • Ну, и файлик web.config поместите такой код:
    <?xml version="1.0"?>
    <configuration>
      <appSettings/>
      <connectionStrings/>
      <system.web>
        <httpModules>
          <add name="MapOrg" type="Microsoft.Crm.MapOrgEngine, Microsoft.Crm, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
          <add name="CrmAuthentication" type="Microsoft.Crm.Authentication.AuthenticationEngine, Microsoft.Crm, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
        </httpModules>
        <identity impersonate="true"/>
        <compilation debug="true">
          <assemblies>
            <add assembly="Microsoft.Crm.Sdk, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
            <add assembly="Microsoft.Crm.SdkTypeProxy, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
          </assemblies>
        </compilation>
      </system.web>
    </configuration>
    
  • Опубликуйте сайт в папку <сайт CRM>\ISV\dashboard (ее Вам нужно предварительно создать). Для этого перейдите Build — Publish Web Site.





Отчет

  • Создайте в Visual Studio новый проект отчета Reporting Services;
  • Настройте Data Source для него (т.е. подключение к Вашему SQL серверу);
  • Создайте новый два новый Data Set’а. В первый поместите такой код:
    select
          'Возможные сделки' as tip,
          COUNT(*) as kolvo
    from
          dbo.FilteredOpportunity as CRMAF_opp
    where
          1=1
          and CRMAF_opp.customerid in (
                select accguid
                from dbo.DashboardOnline
                where userguid = @userid
          )
     
    UNION
     
    select
          'Предложения' as tip,
          COUNT(*) as kolvo
    from
          dbo.FilteredQuote as CRMAF_quo
    where
          1=1
          and CRMAF_quo.customerid in (
                select accguid
                from dbo.DashboardOnline
                where userguid = @userid
          )
     
    UNION
     
    select
          'Заказы' as tip,
          COUNT(*) as kolvo
    from
          dbo.FilteredSalesOrder as CRMAF_ord
    where
          1=1
          and CRMAF_ord.customerid in (
                select accguid
                from dbo.DashboardOnline
                where userguid = @userid
          )
     
    UNION
     
    select
          'Счета' as tip,
          COUNT(*) as kolvo
    from
          dbo.FilteredInvoice as CRMAF_inv
    where
          1=1
          and CRMAF_inv.customerid in (
                select accguid
                from dbo.DashboardOnline
                where userguid = @userid
          )
    

    Этот код вытаскивает данные из CRM в таком виде:

    Возможные сделки 2
    Заказы 1
    Предложения 1
    Счета 1

    А во второй такой код:

    select
          CRMAF_ap.activitytypecodename as tip,
          COUNT(*) as kolvo
    from
          dbo.FilteredActivityPointer as CRMAF_ap
    where
          1=1
          and CRMAF_ap.regardingobjectid in (
                select accguid
                from dbo.DashboardOnline
                where userguid = @userid
          )
    group by CRMAF_ap.activitytypecodename
    

    Данные будет возвращены в виде таблицы:

    Встреча 2
    Письмо 2
    Факс 1
    Электронная почта 1
  • После этого в параметрах появится новый параметр userid. Откройте его свойства и перейдите к разделу и измените параметр Visibility на Hidden;
  • Добавьте на рабочую область два графика: один форонка продаж, а второй круговая диаграмма;
  • В воронку продаж перенесите данные из первого датасета, а в круговую диаграмму – из второго (kolvo в значения, а tip в категории);
  • Измените размер отчета, чтобы более менее соответствовал размеру iFrame’а, в который он будет помещен (по высоте это 230 пикселов, а ширина зависит от Вашего мионитора). Также уберите границу графиков (это делается в его свойствах);
  • Находясь в представлении Design перейдите File — Save xxxxxx.rdl As и сохраните отчет с тем именем, которые Вы указали в функции OnLd в DashboardOnline.htm;
  • Откройты менеджер отчетов Reporting Services. По дефолту путь такой:
    [test]http://<сервер_отчетов>/Reports[/text]
  • Загрузите на Reporting Services только что сохраненный файл RDL.








SiteMap

Теперь нужно дать доступ к страничке DashboardOnline.htm всем юзверам:

  • Выгрузите SiteMap из CRM и откройте в каком-либо текстовом редакторе;
  • Вставьте в нужный Вам раздел такую строчку (ссылку на нашу страничку DashboardOnline.htm):
    <SubArea Id="nav_dOl" Icon="/_imgs/ico_18_salesOverview.gif" Url="http://win-n22hj23d1b1/isv/dashboard/DashboardOnline.htm" Title="Дашбоард2" />

    Замените название сервера на свое;

  • Импортируйте SiteMap обратно в CRM и идите смотреть на дашбоард 🙂


Комментарии (2)
  • Костя 02.07.2010

    Привет. Хороший пример.
    Есть вопрос — не могу указать скрол для iframe, в свойствах прописываю scrolling=»auto» и scrolling=»yes», но пока все никак. Не сталкивался с таким?

  • slivka_83 02.07.2010

    Привет. Сталкивался 🙂 Точнее сталкивался с тем, что страница кэшируется и сколько бы я не менял scrolling в исходниках, на странице это не как не отображалось 🙂 Выяснил это случайно — подождав некоторое время — страница все таки обнавилась 🙂

*

code