Разработка
18
Апр
0

Несколько «владельцев» для одной записи

Есть много случаев, в которых клиенты хотят видеть нескольких «владельцев» каких-либо записей в CRM. Например, это могут быть группы продаж, совместно использующих записи возможных сделок или специалисты службы поддержки, совместно использующие записи обращений. Либо Вы просто хотите назначить «напарника» на случай непредвиденных обстоятельств (например болезни и т.д.). Но к сожалению CRM Microsoft создан таким образом, что только один пользователь (либо организация) может быть «владельцем» какой-либо записи в MS CRM (говорят в 5 версии это исправят 🙂 ).

Давайте рассмотрим сценарий, в котором нужно определенным пользователям предоставить полный доступ к записи на ровне с их владельцем (который, в соответствии с настройками ролей безопасности монопольно владеет своими записями, т.е. другие пользователи не могут их ни читать ни редактировать). Т.к. юзверы обычно ленивые, то необходимо свести к минимуму их телодвижения, но при этом предоставить достаточно информации для восприятия! Другими словами стандартный функционал Общего доступа нам не подходит – слишком много кликов мыши, а чтобы узнать на кого рашарена запись необходимо лезть в соответствующее меню! Поэтому упростим задачу – вынесем на форму какого-либо объекта лукап на объект Пользователь. Соответственно полсе выбра в этом лукапе какого-либо пользователя, запись должна автоматически на него расшариваться. А если удалить его из лукапа или заменить другим пользователем, то общий доступ для этого старого «Совладельца» должен сниматься. Итак…

  • Для начала создайте новый лукап на объект Пользователь (в этом примере будетм эксперементировать с Контактами). Назовите его атрибут, например, new_coowner, с отображаемым именем Совладец и поместите на форму CRM (если хотите, то можете скрыть это поле).
  • Создайте новый плагин, добавьте в него сборки из SDK и подпешите. В сам же код добавьте такой фрагмент:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.Crm.Sdk;
    using Microsoft.Crm.SdkTypeProxy;
    
    namespace ClassLibrary1
    {
        public class Class1 : IPlugin
        {
            public void Execute(IPluginExecutionContext context)
            {
                ICrmService Service = context.CreateCrmService(true);
    
                // Если это операция обновления - снимаем общий доступ со старого Совладельца и выдаем новому
                if (context.MessageName == MessageName.Update)
                {
                    // Если контекст выполнения плагина не содержит "снимков", то прекращаем выполнение
                    if (!context.PreEntityImages.Properties.Contains("PreContactImage") || !context.PostEntityImages.Properties.Contains("PostContactImage"))
                        return;
    
                    // Получаем GUID обновляемой записи  
                    DynamicEntity entity = (DynamicEntity)context.InputParameters.Properties["Target"];
                    Key ContactId = (Key)entity.Properties["contactid"];
    
                    DynamicEntity preContact = (DynamicEntity)context.PreEntityImages.Properties["PreContactImage"];
                    // Проверяем заполнен ли был лукап Совладлец до изменения записи
                    if (preContact.Properties.Contains("new_coowner"))
                    {
                        // Получаем GUID пользователя из лукапа Совладелец переданного в post-снимке PostContactImage
                        Lookup lkpNew_coowner = (Lookup)preContact.Properties["new_coowner"];
                        string New_coownerGuid = lkpNew_coowner.Value.ToString();
    
                        // Отключаем права для старого Совладельца обновляемой записи
                        // Передаем: ссылку на сервис CRM, GUID записи, GUID пользователя и имя объекта
                        unShare(Service, ContactId.Value.ToString(), New_coownerGuid, context.PrimaryEntityName.ToString());
                    }
    
                    DynamicEntity postContact = (DynamicEntity)context.PostEntityImages.Properties["PostContactImage"];
                    // Проверяем заполнен ли лукап Совладлец после изменения записи
                    if (postContact.Properties.Contains("new_coowner"))
                    {
                        // Получаем GUID пользователя из лукапа Совладелец переданного в post-снимке PostContactImage
                        Lookup lkpNew_coowner = (Lookup)postContact.Properties["new_coowner"];
                        string New_coownerGuid = lkpNew_coowner.Value.ToString();
    
                        // Выдаем права новому Совладельцу для обновляемой записи
                        // Передаем: ссылку на сервис CRM, GUID записи, GUID пользователя и имя объекта
                        Share(Service, ContactId.Value.ToString(), New_coownerGuid, context.PrimaryEntityName.ToString());
                    }
                }
                // Если это операция создания записи - выдаем общий доступ Совладельцу
                else if (context.MessageName == MessageName.Create)
                {
                    // Если контекст выполнения плагина не содержит "снимка", то прекращаем выполнение
                    if (!context.PostEntityImages.Properties.Contains("PostContactImage"))
                        return;
    
                    // Получаем GUID создаваемой записи  
                    string ContactId = context.OutputParameters.Properties["id"].ToString();
    
                    DynamicEntity postContact = (DynamicEntity)context.PostEntityImages.Properties["PostContactImage"];
                    if (postContact.Properties.Contains("new_coowner"))
                    {
                        // Получаем GUID пользователя из лукапа Совладелец переданного в post-снимке PostContactImage
                        Lookup lkpNew_coowner = (Lookup)postContact.Properties["new_coowner"];
                        string New_coownerGuid = lkpNew_coowner.Value.ToString();
    
                        // Выдаем права новому Совладельцу для обновляемой записи
                        Share(Service, ContactId, New_coownerGuid, context.PrimaryEntityName.ToString());
                    }
                }
            }
    
            private void unShare(ICrmService service, string recordId, string userId, string entityName)
            {
                try
                {
                    // Создаем объект SecurityPrincipal и задаем для него юзвера
                    SecurityPrincipal principal = new SecurityPrincipal();
                    principal.Type = SecurityPrincipalType.User;
                    principal.PrincipalId = new Guid(userId);
    
                    // Указываем объект для которого выполняем расшаривание
                    TargetOwnedDynamic target = new TargetOwnedDynamic();
                    target.EntityId = new Guid(recordId);
                    target.EntityName = entityName;
    
                    // Составляем запрос
                    RevokeAccessRequest revoke = new RevokeAccessRequest();
                    revoke.Revokee = principal;
                    revoke.Target = target;
    
                    // Выполняем запрос
                    RevokeAccessResponse revokeed = (RevokeAccessResponse)service.Execute(revoke);
                }
                catch (System.Web.Services.Protocols.SoapException ex)
                {
                    throw ex;
                }
                catch (Exception e)
                {
                    throw e;
                }
            }
    
            private void Share(ICrmService service, string recordId, string userId, string entityName)
            {
                try
                {
                    // Создаем объект SecurityPrincipal
                    SecurityPrincipal principal = new SecurityPrincipal();
                    principal.Type = SecurityPrincipalType.User;
    
                    // PrincipalId это Guid пользователя, для которого необходимо расшарить запись
                    principal.PrincipalId = new Guid(userId);
    
                    // Создаем объект PrincipalAccess
                    PrincipalAccess principalAccess = new PrincipalAccess();
    
                    // Задем его свойства
                    principalAccess.Principal = principal;
    
                    // Выдаем права на все действия для Совладельца
                    principalAccess.AccessMask = AccessRights.ReadAccess;
                    principalAccess.AccessMask |= AccessRights.WriteAccess;
                    principalAccess.AccessMask |= AccessRights.DeleteAccess;
                    principalAccess.AccessMask |= AccessRights.AssignAccess;
                    principalAccess.AccessMask |= AccessRights.AppendAccess;
                    principalAccess.AccessMask |= AccessRights.AppendToAccess;
                    principalAccess.AccessMask |= AccessRights.ShareAccess;
    
                    // Задаем целевой объект и запись
                    TargetOwnedDynamic target = new TargetOwnedDynamic();
                    target.EntityId = new Guid(recordId);
                    target.EntityName = entityName;
    
                    // Создаем запрос
                    GrantAccessRequest grant = new GrantAccessRequest();
    
                    // Задаем его свойства
                    grant.PrincipalAccess = principalAccess;
                    grant.Target = target;
    
                    // Выполняем запрос
                    GrantAccessResponse granted = (GrantAccessResponse)service.Execute(grant);
                }
                catch (System.Web.Services.Protocols.SoapException ex)
                {
                    throw ex;
                }
                catch (Exception e)
                {
                    throw e;
                }
            }
        }
    }
    

    логика работы у этого кода такая:

    • Если происходит создание записи , то расшариваем эту запись на пользователя выбранного в лукапе new_coowner (нам не нужно проверять заполнен ли этот лукап, поскольку при регистрации плагина мы укажем реагировать только если это поле заполнено);
    • Если происходит обновление записи, то
      • смотрим был ли заполнен лукап new_coowner до обновления и если да, то снимаем с этого пользователя общий доступ;
      • смотрим заполнен ли лукап new_coowner после обновления и если да расшариваем на него текущую запись.


  • Запустите Plugin Registration Tool и зарегистрируйте сборку нашего плагина. Для это сборке создайте:
    • Шаг на Создание записи, с реакцией только на поле new_coowner. Для этого шага зарегистриует один снимок:
      • С именем PostContactImage и атрибутом new_coowner.
    • Шаг на Обновление записи, с реакцией только на поле new_coowner. Для этого шага зарегистриует два снимка:
      • С именем PreContactImage, стадия Pre, атрибут new_coowner;
      • С именем PostContactImage, стадия Post, атрибут new_coowner.



  • Тестируем. Создайте новую запись Контакта, либо откройте существующую. В ней выберите какого-либо пользователя в поле Совладелец и сохраните карточку. Теперь откройте форму Общего доступа и убедитесь что выбранный пользователь автоматически получил все права на эту запись!


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

*

code