Кастомизация
19
Дек
15

Microsoft Dynamics CRM: Атака клонов

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

Бизнес-процесс

Самый простой способ это конечно же использовать Бизнес-процессы:

  • Создайте новый бизнес процесс в отношении объекта, который собираетесь копировать;
  • Условием запуска поставьте «По требованию»;
  • Добавьте новый шаг «Создать запись». А в качестве создаваемой записи укажите тот же объект, для которого создавали Бизнес-процесс (т.е. который собираемся дублировать);
  • В настройках этого шага заполните поля, которые подлежат копированию в дубликат записи (если нужно полная копия – то все);
  • Опубликуйте Бизнес-процесс;
  • Откройте какую-либо запись, которая подлежит копированию (или выделите ее в представлении);
  • Нажмите Запустить Бизнес-процесс на панели инструментов, выделите Бизнес-процесс, который Вы создали для копирования и жмите ОК. Через некоторое время в представлении этого объекта появится дубликат записи, который Вы можете открыть и редактировать.



Вы можете сократить количество шагов по копированию записей, путем вынесения запуска Бизнес-процесса в кнопку (см. статью Запуск бизнес-процессов с помощью JavaScript).

Маппинг

Маппинг – это простой способ скопировать значения полей с родительской записи в связанную. А так как маппинг возможен между одним и тем же типом объектов, то этим можно воспользоваться для создания копий записей.

  • Сначала создайте отношения 1:N для объекта записи которого собираетесь копировать. Причем и Основным и Связанным должен быть один и тот же объект.
    Учтит, что если Вы хотите видеть все «клоны» исходной записи на ее левой навигационной панели, то выберите значение в ниспадающем списке «Параметры отображения»;
  • Сохраните отношения. После этого на левой навигационной панели появится пункт Маппинг – перейдите к нему и создайте сопоставления для всех полей, значения которых хотите перенести в копию (сопостовления должны быть один-в-один, т.е., например, полю Имя должно быть также сопоставлено поле Имя).
  • Если Вы хотите на клоне видеть из какой записи она была создана, то вынесете на форму лукап созданной связи 1:N.
  • На онлоад формы повесьте такую функцию:
    Clone = function() {
    
    	var cloneUrl  = location.pathname + "?";  
    	cloneUrl += "_CreateFromType=" + crmForm.ObjectTypeCode;
    	cloneUrl += "&_CreateFromId=" + crmForm.ObjectId;
        if (crmForm.ObjectTypeCode > 9999)
        	cloneUrl += "&etc=" + crmForm.ObjectTypeCode;
        cloneUrl += "#";
        
    	var cloneFeatures = 'toolbars=0,status=1,width=' + document.body.offsetWidth + 'height=' + document.body.offsetHeight;   
    
    	window.open(cloneUrl,'',cloneFeatures);  
    }
    

    Эта функция открывает новое окно и передает в URL параметры необходимые для имитации создания связанной записи. В результате чего задействуется стандартный функционал маппинга и происходит копирование необходимых полей.

  • А для вызова этой функции добавьте в ISV.config следующую кнопку:
    <ToolBar ValidForCreate="0" ValidForUpdate="1">
    	<Button Icon="/_imgs/treeOn.gif" JavaScript="Clone();">
    		<Titles>
    			<Title LCID="1049" Text="Клонировать" />
    		</Titles>
    		<ToolTips>
    			<ToolTip LCID="1049" Text="Клонировать" />
    		</ToolTips>
    	</Button>
    	<ToolBarSpacer />
    </ToolBar>
    
  • Тестируем…




JavaScript

В осное этого метода лежат способ получения значений с родительского окна с помощью javaScript’а. Только дополненный кнопочкой по которой происходит копирование, а также перенос значения всех полей с родительской формы в запись-клон.

  • Экспортируйте ISV.Config и добавьте для нужного Вам объекта следующую кнопку:
    <ToolBar ValidForCreate="0" ValidForUpdate="1">
    	<Button Icon="/_imgs/treeOn.gif"
    		JavaScript="
    		var ObjectTypeCode = crmForm.ObjectTypeCode;
    		var EntUrlInfo = GetWindowInformation(ObjectTypeCode);
    
    		var EntWindowFeatures = 'toolbars=0,width=' + EntUrlInfo.Width + ',Height=' + EntUrlInfo.Height + ',Left=10,top=90';
    		var EtcQsParameter = (ObjectTypeCode > 9999)? '?etc=' + ObjectTypeCode : '';
    		var EntWindowUrl = '/' + ORG_UNIQUE_NAME + '/' + EntUrlInfo.Url + EtcQsParameter;
    		var EntWindowName = 'Cloned' + crmForm.ObjectTypeName;
    		window.open( EntWindowUrl , EntWindowName , EntWindowFeatures );
    		" Client="Web">
    		<Titles>
    			<Title LCID="1049" Text="Клонировать" />
    		</Titles>
    		<ToolTips>
    			<ToolTip LCID="1049" Text="Клонировать" />
    		</ToolTips>
    	</Button>
    	<ToolBarSpacer />
    </ToolBar>
    
  • На онлоад формы этого же объекта поместите слудующий код:
    // определяем что это процесс клонирования
    if( window.name == 'Cloned' + crmForm.ObjectTypeName && crmForm.FormType == 1 ) {
    	GetOriginalEntityFields();
    }
    
    function GetOriginalEntityFields() {
    
    
        // проеряем наличие родителького окна
        if( !opener ) {
            alert('Родительское окно закрыто. Клонирование не возможно!');
            return;
        }
    
        var re = new RegExp("INPUT|TEXTAREA|SELECT","gi");
        // Получаем все поля родительской записи
        var originalEntityFormElements = opener.document.all.crmForm.all;
        // Получаем все поля клонируемой записи
        var currentEntityFormElements = crmForm.all;
    
        // Перебираем все поля исходной записи
        for( var i = 0 ; i < originalEntityFormElements.length ; i++ ) {
    
             var OriginalElement = originalEntityFormElements[ i ];
             var OriginalElementId = OriginalElement.id;
    
             if( OriginalElementId != '' ) {
             
                 var currentElement = currentEntityFormElements[OriginalElementId];
    
                 // копируем значение полей из исходной формы в клонируемую
                 if( typeof(currentElement.req) != "undefined" ) {
                     currentElement.DataValue = OriginalElement.DataValue;
                     currentElement.Disabled = OriginalElement.Disabled;
                 } else if ( re.test(currentElement.tagName) ) {
                     currentElement.value = OriginalElement.value;
                 }
             }
        }
    }
    

Все 🙂 тестируем…



ASPX + AJAX

Самый сложный, но вместе с тем и самый «красивый» способ. Реализовывать его будем с помощью ASP. Тут нам понадобится Visual Studio 2008 и кое-какие дополнительные компоненты…

  • Сначала скачайте и установите Microsoft ASP.NET 2.0 AJAX Templates for Visual Studio 2008;
  • После этого вы сможите создать в Visual Studio 2008 новый Web Site проект AJAX 1.0-Enabled ASP.NET 2.0 (перейдите File — New — Web Site…). Что Вам и нужно сделать 🙂
  • Перейдите Website — Start Options — назакладке Build в качестве фреймворка выберите .NET 3.5;
  • Установите последнюю версию SDK. Откройте диалог Website — Add Reference — вкладка Browse — доберитесь до папки установки SDK, далее sdk\bin\ — выделите два файла:
    • Microsoft.Crm.Sdk.dll
    • Microsoft.Crm.SdkTypeProxy.dll
  • Откройте code behind (View — Code) и добавьте в конец соответствующего списка следующие строки:
    • using Microsoft.Crm.Sdk;
    • using Microsoft.Crm.Sdk.Query;
    • using Microsoft.Crm.SdkTypeProxy;




  • Вернитесь к визуальному редактору View — Designer. Щелкните в нижней части экрана Design, чтобы перейти к визуальному интерфейсу редактирования. С панели Toolbox из области AXAX Extension пертащите на лист следующие компоненты:
    • ScriptManager (по умолчанию уже должен быть)
    • UpdatePanel
      Перетащите следующие компоненты (из области Standart) внутрь объекте UpdatePanel и задайте для них указанные свойства на панели Properties:

      • .NET Button, параметры:
        • Enabled = true
        • Name = Button1
      • .NET Hyperlink, параметры:
        • Enabled = true
        • Visible = false
        • Name = Hyperlink1
        • ForColor = #0000CC
        • Underline = True
    • UpdateProgress
      Внутрь этого компонента нам нужно поместить рисунок, который будет отображаться пока будет происходить копирование записи. Сначала скачайте Animated_thumb.gif куда-нибудь на компа. Затем в VS перейдите Website — Add Existing Item… и выделите этот рисунок. После этого он появится в Solution Explorer, откуда и перетащите его в UpdateProgress. Под рисунком напишите какой-нибудь текст, который буде сообщать пользователю, что идет процес копирования.



  • Дважды кликните на кнопке, в результате чего в code behind создастся обработчик события клика. Теперь внутрь этого события нужно добавить следующий код (измените в нем путь к CRM серверу и логин/пароль учетки, под которой будем подключаться):
    // Разбираем переменный переданные в строке запроса
    if (Request.QueryString.Count > 0)
    {
    	string _org = Request["orgname"].ToString();
    	Guid _entityid = new Guid(Request["id"].ToString());
    	string _entitytypename = Request["typename"].ToString();
    
    	// Устанавливаем цель запроса
    	TargetRetrieveDynamic _entitytoclone = new TargetRetrieveDynamic();
    	_entitytoclone.EntityName = _entitytypename;
    	_entitytoclone.EntityId = _entityid;
    
    	// Выполняем запрос и клонируем объект
    	try
    	{
    		DynamicEntity _entity;
    		RetrieveResponse _response;
    		RetrieveRequest _request = new RetrieveRequest();
    
    		// Возвращаем все столбцы, которые содержат данные
    		_request.ColumnSet = new AllColumns();
    
    		// Используем объект Dynamic Entity
    		_request.ReturnDynamicEntities = true;
    		_request.Target = _entitytoclone;
    
    
    		// Возвращаем объект для клонирования
    		CrmAuthenticationToken _token = new CrmAuthenticationToken();
    		_token.OrganizationName = _org;
    		_token.AuthenticationType = 0;
    
    		CrmService _service = new CrmService();
    		_service.PreAuthenticate = false;
    		_service.CrmAuthenticationTokenValue = _token;
    		_service.Credentials = new System.Net.NetworkCredential("Administrator", "1qaz@WSX");
    		_service.Url = "http://crm2008/mscrmservices/2007/crmservice.asmx";
    
    		_response = (RetrieveResponse)_service.Execute(_request);
    		_entity = (DynamicEntity)_response.BusinessEntity;
    
    		// Удалите Key Properties (CustomerAddressID и т.д), иначе это вызовет о нарушении PK в SQL
    		foreach (Property p in _entity.Properties)
    		{
    			if (p is KeyProperty)
    			{
    				_entity.Properties.Remove(p.Name);
    			}
    		}
    
    		// Создаем клонируемый объект
    		Guid _newguid = (Guid)_service.Create(_entity);
    		string _url = "";
    		HyperLink1.Text = "Запись успешно скопирована!</br>Кликните на ссылке чтобы открыть ее!";
    		_url = SetLinkButtonProperties(_entityid.ToString(), _entitytypename, _org);
    		HyperLink1.Attributes.Add("onclick", "window.open('" + _url + "','','toolbars=0,width=1000,Height=600,Left=10,top=90');window.close();");
    		HyperLink1.Visible = true;
    		_service.Dispose();
    
    	}
    	catch (Exception x)
    	{
    		// Обработка исключений
    	}
    }
    

    А чуть ниже него добавьте такую функцию:

    // Функция формирует URL клонированной записи
    private string SetLinkButtonProperties(string id, string name, string org)
    {
    	string path = "../../../";
    	switch (name)
    	{
    		case "account":
    			path += org + "/sfa/accts/edit.aspx?id=" + id;
    			break;
    		case "contact":
    			path += "/sfa/conts/edit.aspx?id=" + id;
    			break;
    		case "opportunity":
    			path += "/sfa/conts/edit.aspx?id=" + id;
    			break;
    		case "lead":
    			path += "/sfa/lead/edit.aspx?id=" + id;
    			break;
    		case "incident":
    			path += "/cs/cases/edit.aspx?id=" + id;
    			break;
    		default:
    			path += "/userdefined/edit.aspx?id=" + id + "&etn=" + name;
    		break;
    	}
    	return path;
    }
    
  • Перейдите снова в Designer, затем в нижней вкладке к Source и добавьте под кодом кнопки следующий JavaScript-элемент:
    <script language='javascript' type="text/javascript">
        var _isInitialLoad = true;
        function pageLoad(sender, args)
        {
            if(_isInitialLoad)
            {
                _isInitialLoad = false;
                //  Имитируем нажатие на кнопку
                __doPostBack('<%= this.Button1.ClientID %>','');        
            }
        }
    </script>
    
  • Для HyperLink и рисунка с текстом под ним добавьте обрамление из div-элемента:
    <div align="center">
    
    </div>
    
  • А внутрь тега Button добавьте код скрытия кнопки:
    STYLE="display:none"
    



  • На этом VS-часть закончена, теперь нужно это приложение публиковать. Создайте в папке <сайт CRM>\ISV подпапку crmextensions и скопируйте в нее содержимое проекта Website’а. Затем откроте IIS Manager, доберитесь до папки ISV в дереве сайта, щелкните правой кнопкой по папке crmextensions — Convert to Application и преобразуйте папку в приложение.
  • Экспортируйте ISV.Config и добавьте следующий код для формы объекта, записи которого хотите клонировать:
    <ToolBar ValidForCreate="0" ValidForUpdate="1">
    	<Button Icon="/_imgs/treeOn.gif" Url="/isv/crmextensions/Default.aspx" PassParams="1" WinParams="dialogHeight:100px;dialogWidth:300px;" WinMode="1">
    		<Titles>
    			<Title LCID="1049" Text="Клонировать" />
    		</Titles>
    		<ToolTips>
    			<ToolTip LCID="1049" Text="Клонировать" />
    		</ToolTips>
    	</Button>
    </ToolBar>
    

    Импортируйте ISV.Config обратно;



  • На этом все 🙂 открывайте карточку и жмите кнопку 🙂



P.S. исходники проекта 🙂

Комментарии (15)
  • Анатолий 19.12.2009

    Здравствуйте.
    А что касается раздела JavaScript — нужно ли что то менять для кустомной сущности, а то что то ни как не могу отладить.
    А вообще БОЛЬШОЕ Вам спасибо что затронули этот вопрос (Клонирование)

  • slivka_83 19.12.2009

    Повесил его на кастомную сущность. НИЧЕГО не менял. Вообще 🙂 отработало как часы 🙂

  • Анатолий 19.12.2009

    Что то не пойму в чем у меня ошибка — кнопка жмется окно открываеца с адресом https://сервер/организация/userdefined/edit.aspx?etc=10000 (почему то без # в конце) и с 404 ерором

    Прим. Извините уж за назойливость.

  • slivka_83 19.12.2009

    # ни на что не влияет. CRM сама его допишет если нужно 🙂 А Вы можете вручную вставить в адресную строку этот URL (https://сервер/организация/userdefined/edit.aspx?etc=10000) и посмотреть создасться ли карточка?

  • Анатолий 19.12.2009

    Все тоже самое.
    Я кажется понял что не так. у родительской карточки адрес https://сайт/userdefined/edit.aspx?id={87FA1C65-D4CA-DE11-AEC6-00155D00F502}&etc=10000# а у дочерней, дочерней которая создается https://сайт/организация/userdefined/edit.aspx?etc=10000 т. е. /организация/ лишнее звено. А как быть?

  • Анатолий 19.12.2009

    Т.е. видимо что то надо предпринять с значением ORG_UNIQUE_NAME, но только что?

  • slivka_83 19.12.2009

    Я не совсем понял что значит «Все тоже самое»? т.е. при ручном вводе урлы такая же ошибка (404)?

  • Анатолий 19.12.2009

    Да да при ручном вводе урлы с новой карточки которая создается в отдельном окне в новое окно — 404 еррор, но при этом если я из этой урлы удаляю /организация/, открывается новая пустая карточка.

  • slivka_83 19.12.2009

    Очень странное поведение… ну тогда попробуйте из строчки:
    var EntWindowUrl = ‘/’ + ORG_UNIQUE_NAME + ‘/’ + EntUrlInfo.Url + EtcQsParameter;
    убрать:
    ‘/’ + ORG_UNIQUE_NAME +
    Т.е. в итоге будет так:
    var EntWindowUrl = ‘/’ + EntUrlInfo.Url + EtcQsParameter;

  • Анатолий 19.12.2009

    Yes!!! Все арботает. Спасибо!!!

  • Евгений 19.12.2009

    А можно каким нибудь образом организовать копирование записей из представления. Т.Е. выбрал запись в представлении, нажал кнопку копировать, и у тебя точно такая же запись, только с другим GUID 🙂 Вариант с Бизнес-процессом не подходит, приходится ждать пока он отработает 🙁

  • slivka_83 19.12.2009

    Можно 🙂
    Создаем с помощью ISV.config’а http://mmcrm.ru/?p=864 новую кнопку над представлением.
    Получаем GUID записи http://mmcrm.ru/?p=844
    Полуйчаем все необходимые поля http://msdn.microsoft.com/en-us/library/cc677076.aspx или http://msdn.microsoft.com/en-us/library/cc677073.aspx
    Ну и создаем новую запись http://msdn.microsoft.com/en-us/library/cc677070.aspx

  • loggerzd 19.12.2009

    Сделал всё, как описано в разделе Маппинг, но у меня даже кнопка не появилась 🙂 ISV.config импортировал. Не понимаю.

  • slivka_83 19.12.2009

    Включите отображение ISV кастомизации в CRM системе: Параметры – Администрирование – Системные параметры – вкладка Настройка – секция Настраиваемые меню и панели инструментов – добавьте те клиенты, в которых хотите показывать новые кнопки 🙂

  • loggerzd 19.12.2009

    Точно. Спасибо! 🙂

*

code