AXForum  
Вернуться   AXForum > Блоги > CRM, SharePoint и Черная Магия
All
Забыли пароль?
Зарегистрироваться Правила Справка Пользователи Сообщения за день Поиск Все разделы прочитаны

Добро пожаловать в мой блог! Изначально он не задумывался как блог CRM разработчика, но жизнь сама внесла нужные коррективы. Тут я публикою все свои наблюдения относительно обозначенных в заголовке систем. Если Вы найдете в нем что-то интересное для Вас, как для заказчика, то буду рад сотрудничать с Вами! В моей компетенции 100% задач по MS CRM 3.0/4.0/2011:
  • Консалтинг
  • Проектирование
  • Разработка
  • Обучение


MVP 2010, 2011
Оценить эту запись

Управление видом формы из бизнес процесса (ректальная хирургия гланд)

Запись от Артем Enot Грунин размещена 02.04.2010 в 12:43

Случалось ли вам при продаже системы заявить функционал, который потом приходится реализовывать? Иногда реализовывать приходится какую-нибудь муру, которую бос или продажник сдуру пообещали заказчику, но бывают случаи, когда жизнь ставит тривиальные, вроде бы задачи, в таком неожиданном ракурсе, что систему становится не узнать. Вот пример из жизни. Обещали вполне обычную вещь: организовать бизнес процессы продажи, обещали обычными словами вроде "система будет навязывать пользователю определенную последовательность действий и следить за их выполнением". Мы то с вами понимаем, что речь идет о бизнес процессе, который будет ставить пользователю задачи и с места не тронется, пока тот их не завершит... и все. Что в этот момент подумал заказчик? Он подумал, что она будет долбить пользователя подсказками и напоминаниями, будет заставлять его заполнять поля, причем правильно и в нужной последовательности и уж точно не позволит ему влезть в "Б" пока не завершено "А". И вот тут облом. Делать поля обязательными бизнес процессу не под силу. Равно как и скрывать или отключать элементы интерфейса. С подсказками тоже туго... Тогда на помощь приходит Java Script, скажете вы и будете правы! Но что если заказчик клинический фанат систематизации и у вас будет по процессу на каждый вид продукции, с подпроцессами на каждый вид клиента или от объема сделки? В этом случае вы сталкиваетесь с н-мерной матрицей процессов, которые, кстати, могут со временем еще меняться в зависимости от требований клиента! В результате, как бы хорошо вы не писали и документировали ваш код, уже никто и никогда не сможет учесть всех нюансов его исполнения в рамках 60 возможных процессов, в ситуации когда в каждый момент времени исполняется 4 разных версии одного и того же процесса. Кому-то ситуация покажется надуманной, но кто-то видел это вживую...
Вариант решения проблемы подкрался ко мне неожиданно. Изначально он вызывал у меня только улыбку, но чем дальше тем больше я убеждался, что может и это и дебилизм, но он работает и работает как надо. Решение: выделяем на форме большое многострочное текстовое поле (например Описание у сделки, если оно не используется), БИЗНЕС ПРОЦЕССОМ ПИШЕМ ТУДА JavaScript КОД, после чего исполняем его мастер скриптом из формы.

+ Работает!
+ Версионность кода и бизнес процесса синхронизированы
+ Освобождает форму от нагромождения скриптов
+ Версионность и переносимость кастомизаций сохранена

- Хранить исполняемый код в базе - "Это ненормально, противоестественно и просто противно" © Конфуций.
- Такой скрипт не кэшируется и исполняется браузером медленнее
- Существенные ограничения на длину кода
- Могут быть проблемы с областью выполнения кода
- Поле вылезет на форму печати
- Процесс обновит запись асинхронно. Если форма осталась на экране она останется в старом состоянии пока не будет обновлена.

В защиту:
1. При закрытии сделки, оставшийся код можно зачистить плагином или тем же процессом. Сжатие пустых полей SQL Server 2008 не даст пропасть свободному пространству.
2. В 3.0 кэширования пользовательских скриптов вообще не было, так что весь код только так и исполнялся!
3. Ограничение вполне преодолимо, если держать на форме готовую библиотеку функций, а из поля исполнять только вызовы к ним.
4. Как преодолеть проблему изложено тут: http://piecesofrakesh.blogspot.com/2...oiler-its.html
5. Есть куча постов о том как повлиять на эту форму
6. Проблема остается при любом подходе - WF асинхронный и никак иначе.

Итак приступим! В качестве полигона инструкций я выбрал поле description Возможной сделки. Это поле вообще очень редко заполняется, тем более что пользователей можно приучить к комментариям (примечаниям). На OnLoad формы поместим приведенный ниже код. Он инициализирует библиотеку функций, которые я нахожу наиболее востребованными (всегда можно будет дополнить!). Это:

+ Создание уведомлений
+ Блокировка/Активация полей
+ Скрытие/Показ элементов интерфейса
+ Изменение уровня требования полей
+ Заполнение полей

+ ... фетчи и пр. добавлять не стал
+ Собственно запуск скрипта из поля в соответствии с рекомендациями из п.4

Как это выглядит:
Код:
var oScriptField = crmForm.all.description;

ScriptMonkey(oScriptField);

function ScriptMonkey(oScriptField)
{
    // Notifications
    var ERROR = 1;
    var WARNING = 2;
    var INFORMATION = 3;
    var oNotifications = crmForm.all.Notifications;

    // Field Requirements Levels
    var NOCONSTRAINT = 0;
    var RECOMMENDED = 1;
    var REQUIRED = 2;
    var RECOMMENDED_LABEL = "Recommended";

    function runDefault()
    {
        // выполняйте тут действия по умолчанию
    }

    smAlarm = function(oText, oId)
    {
        oNotifications.AddNotification(oId ? oId : ERROR, ERROR, "notused", oText);
    }

    smNotify = function(oText, oId)
    {
        oNotifications.AddNotification(oId ? oId : INFORMATION, INFORMATION, "notused", oText);
    }

    smWarn = function(oText, oId)
    {
        oNotifications.AddNotification(oId ? oId : WARNING, WARNING, "notused", oText);
    }

    smDisable = function(oElementID)
    {
        var oControl = crmForm.all(oElementID);
        if (oControl != null) oControl.Disabled = true;
    }

    smEnable = function(oElementID)
    {
        var oControl = crmForm.all(oElementID);
        if (oControl != null) oControl.Disabled = false;
    }

    smHide = function(oElementID)
    {
        var oControl = document.all(oElementID);
        var oControl_c = document.all(oElementID + "_c");
        var oControl_d = document.all(oElementID + "_d");
        
        if (oControl != null) oControl.style.display = "none";
        if (oControl_c != null) oControl_c.style.display = "none";
        if (oControl_d != null) oControl_d.style.display = "none";
    }

    smShow = function(oElementID)
    {
        var oControl = document.all(oElementID);
        var oControl_c = document.all(oElementID + "_c");
        var oControl_d = document.all(oElementID + "_d");

        if (oControl != null) oControl.style.display = "inline"; // Nav, field: style = inline
        if (oControl_c != null) oControl_c.style.display = "block"; // Tab, field_c, field_d: style = block
        if (oControl_d != null) oControl_d.style.display = "block";
    }

    smSetValue = function(oElementID, oVal)
    {
        var oControl = document.all(oElementID);
        if (oControl != null) oControl.DataValue = oVal;
    }

    smSetRequiredLevel = function(oElementID, oLevel)
    {
        crmForm.SetFieldReqLevel(oElementID, oLevel);

        if (oLevel == RECOMMENDED) // странный баг (некритичный, но...)
        {
            var oControl = document.all(oElementID);
            var oControl_c = document.all(oElementID + "_c");

            oControl.req = RECOMMENDED;
            var oImgs = oControl_c.getElementsByTagName("IMG");
            if (oImgs.length != 0)
            {
                oImgs[0].alt = RECOMMENDED_LABEL;
                oImgs[0].src = "/_imgs/frm_recommended.gif";
            }
        }
    }

    function InitRun()
    {
        var oCode = oScriptField.DataValue;

        // hold Shift to enter debugg mode
        if (event.shiftKey)
        {
            debugger;
            smAlarm("in debugg mode", 999);
        }
        else
        {
            //oScriptField.Disabled = true;
            smHide(oScriptField.id);
        }

        // execute default code if script field is empty
        if (oCode == null || oCode == "")
        {
            runDefault();
        }
        else
        {
            var oScript = "with (window) {";
            oScript += oCode;
            oScript += "}";

            eval(oScript);
        }
    }

    InitRun();
}
Код позволяет отлаживаться не покидая форму. Если при запуске формы зажата клавиша Shift, то поле со скриптами не будет блокироваться и прятаться. Просто пишите код, сохраняйте, получайте результат. Готовые отлаженные снипеты можно переносить в процесс. Этот метод, к слову, поможет следить за длинной скрипта - вы просто не сможете вставить в поле больше кода, чем оно может вместить.

Нажмите на изображение для увеличения
Название: debugg.jpg
Просмотров: 1621
Размер:	117.2 Кб
ID:	51

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

Распишем действия по этапам процесса, например так:
Код:
// Навязываем последовательность заполнения полей

// Шаг 1
smNotify("Please fill Requirement1 field");
smSetRequiredLevel("new_requirement1", 2);
smDisable("new_requirement2");
smDisable("new_requirement3");

// Шаг 2
smNotify("Please fill Requirement2 field");
smSetRequiredLevel("new_requirement2", 2);
smDisable("new_requirement1");
smDisable("new_requirement3");

// Шаг 3
smNotify("Please fill Requirement3 field");
smSetRequiredLevel("new_requirement3", 2);
smDisable("new_requirement1");
smDisable("new_requirement2");
Для это примера у Сделки было создано три новых атрибута Requirement1(2,3): new_requirement1(2,3).
От процесса мы будем требовать следить, чтобы они заполнялись по очереди и каждое поле требовало заполнения на своем этапе. Например итак:

Нажмите на изображение для увеличения
Название: wf.jpg
Просмотров: 1515
Размер:	217.1 Кб
ID:	52

В блоках изменения записи, на форму вставляется приведенный выше код.
Результат работы:

Шаг 1:

Нажмите на изображение для увеличения
Название: step1.jpg
Просмотров: 1466
Размер:	59.1 Кб
ID:	53

Шаг 2:

Нажмите на изображение для увеличения
Название: step2.jpg
Просмотров: 1592
Размер:	58.8 Кб
ID:	54

Шаг 3:

Нажмите на изображение для увеличения
Название: step3.jpg
Просмотров: 1455
Размер:	59.9 Кб
ID:	55

Разумеется применений может быть и больше, а задачи могут быть изощреннее. Как бы там ни было, их всегда можно поделить на атомарные задачи с параметрами и поместить в библиотеку ScriptMonkey. Не забывайте, что коды обращений к библиотеке хранятся в базе и исполняются при загрузке формы. Это обозначает, что если вы удалите из нее какую-либо функцию или измените ее сигнатуру, пользователь получит ошибки. Так же не следует злоупотреблять загружаемыми инструкциями: чем больше кода будет в библиотеке, тем проще будет корректировать его логику, не задевая сам процесс и не порождая его новые версии.

p. s. Мне кажется, что я не первый кто до этого додумался, и мог видеть нечто подобное в чужих постах. Google мне не помог, но если вы знаете возможный первоисточник, пожалуйста, напишите в комментарии и я сошлюсь на него в статье.
Размещено в CRM
Просмотров 30933 Комментарии 1
Всего комментариев 1

Комментарии

  1. Старый комментарий
    Интересная идея, спасибо за описание
    Запись от Roman08 размещена 02.04.2010 в 22:11 Roman08 is offline
 


Рейтинг@Mail.ru
Часовой пояс GMT +3, время: 09:12.