вторник, 16 октября 2012 г.

Автоматизируем бизнес-процесс на SharePoint подручными средствами

 

Введение

Здравствуйте!
Хочу поделиться опытом автоматизации отдельно взятого бизнес-процесса на платформе Sharepoint 2010.
Думаю, эта статья будет интересна в первую очередь тем пользователям, кто по какой-то причине не хочет писать код в Visual Studio. Она о том, как можно обойти ограничения штатного функционала SharePoint, используя различные подручные решения.

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

  • Система согласования маркетинговых акций - регион предлагает реализацию акции и затем она проходит последовательность согласований и дополнений - вплоть до центра. Каждый шаг соовождается поручениями для определенных пользователей. В конечном счете акция может быть отправлена на реализацию или отклонена
  • Система сбора идей от сотрудников - сотрудник предлагает идею и далее может наблюдать, как она проходит последовательность доработок и одобрений - вплоть до момента приема ее в работу, либо возврата на доработку
  • ... и много-много подобных: работа с проблемами, багами и инцидентами, работа с претензиями, работа с  - словом любая задача, где требуется последовательное рассмотрение несколькими пользователями для принятия решения.

Механизм у всех подобных систем примерно одинаков: в общем случае необходим список для хранения основных сущностей: акция, идея, заказ, проблема и т.д.  Далее - необходим список задач для формирования поручений. Кроме того, наверняка понадобится некоторое количество списков-справочников, например статусы, роли, регионы, филиалы и т.д.

У платформы есть все необходимое для этого: возможность быстро строить списки для основных данных, назначаемых задач и справочников, уже синхронизированные с АД пользователи, готовый механизм раздачи доступов и многое другое. Однако для того чтобы полностью автоматизировать процесс и обойтись без координатора, чьей задачей будет назначать задачи-поручения, обрабатывать ответы и переводить нашу сущность  из статуса в статус - для этого как минимум необходимо задействовать рабочие процессы - workflow. С них и начнём.

Постановка задачи

Мы рассмотрим частный случай бизнес-процесса - назовём его "Заказы на регламентные процедуры"
Вкратце: есть список готовых общепринятых регламентных процедур - Регламентов. К прямому созданию, изменению или удалению элементов этого списка процедур никто не имеет доступа, однако любой пользователь может предложить создание, изменение или удаление процедуры.
Для того, чтобы это произошло, он создает т.н Заказ. Заказ проходит определенную последовательность шагов-статусов, на каждом из которых определенным ролям назначаются поручения. От того, с каким решением будет закрыто поручение, зависит в какой статус перейдет Заказ. Кроме этого, последовательность шагов индивидуальна для заказов на создание, изменение и удаление, а также зависит от дополнительного признака экспресс, который позволяет пройти Заказу по упрощенной схеме. Таким образом, на каждом шаге то, куда пойдет Заказ, зависит от трех условий: от его типа, от наличия признака экспресс и от решения пользователя.
Когда Заказ пройдёт цепочку согласований и дополнений, автоматически создастся/изменится/удалится соответствующий Регламент.

Общая архитектура решения

Основные списки, созданные для решения задачи:

  • Регламенты
  • Заказы
  • Задачи
  • …+ Различные справочники

Схема процесса:

Capture

Автоматизация процесса

На скриншоте ниже похожая схема, выполненная с помощью Visio

Capture1

Её можно экспортировать в MS SharePoint Designer – автоматически будет создан рабочий процесс с условиями “если… то…” и задачками, но даже эта упрощённая схема выглядит весьма громоздкой. Особенно если учесть то, что практически на каждом шаге заказа надо выполнить дополнительные активности – например, отправить письмо, в некоторых шагах требуется внести изменения в другие списки. Это делает линейный рабочий процесс (а SP Designer позволяет делать только линейные) сложноприменимым к нашей ситуации.

Для решения этой задачи можно использовать вот такую комбинацию из двух рабочих процессов, назовём её “кольцо”:

Capture2

Рабочий процесс “кольцо” с успехом заменяет линейный workflow.
Идея незамысловатая - у нас уже есть 2 списка, список заказов и список задач. На список заказов мы навешиваем несложный рабочий процесс, который смотрит на текущий статус Заказа и в зависимости от этого создаёт ту или иную задачу. На список задач мы навешиваем ещё более несложный рабочий процесс, который при завершении задачи обновляет статус в связанном Заказе, тем самым инициируя повторное выполнение рабочего процесса на Заказе.

Для того, чтобы не описывать все шаги и переходы непосредственно в теле workflow, создадим ещё несколько списков-справочников:

  • tasklist – список всех возможных задач, назначаемых пользователям при переходе на очередной шаг
  • logicoftransitions – логика переходов. Список, состоящий из полей
    • taskfrom – задача, из которой надо выполнить переход
    • condition1 – условие 1 (в нашем случае, это решение Да или Нет, принятое пользователем по предыдущей задаче)
    • condition2 – условие 2 (в нашем случае, наличие или отсутствие признака “экспресс”)
    • condition3 – условие 3 (в нашем случае, тип заказа – создание РП, изменение РП, удаление РП)
    • taskto – задача, в которую надо перевести процесс с учётом предыдущих условий
    • sendemail - поле  Да/Нет, посылать ли письмо при выполнении условия
    • emailsubj – тема письма
    • emailtext – текст письма
    • emailsendto – адресаты письма

Этих списков достаточно для автоматизации процесса. Разумеется, если мы оставим поле taskfrom пустым – это будет означать, что задача первая и не имеет предшествующих задач. Пустое поле taskto означает отсутствие следующего шага – задачи, и если нужно сделать что-то помимо отправки письма, эту ситуацию нужно дополнительно обработать в теле workflow.

Итоговый workflow на списке Заказы получается достаточно коротким – судите сами (картинка разбита на 2 половины):

Capture3_1Capture3_2

workflow для списка Задачи и вовсе содержит только одну активность

Capture4

Есть ещё один нюанс. В рабочем процессе нельзя просто так взять и выбрать элемент из списка по совпадению нескольких полей. Одно совпадающее поле - это пожалуйста, но не больше. Однако проблема решается элегантным способом. Нам нужно навесить небольшой рабочий процесс или обработчик или даже просто вычисляемое поле на список logicoftransitions - его задачей будет просто заполнить скрытое текстовое поле, которое мы и станем использовать для сравнения. Так, например, если шаг зависит от состояния 'имя задачи',решение'да/нет','признак экспресс' и 'тип заказа' - то соответственно строка будет выглядеть например так 'согласование руководителя-да-да-создание заказа'. В основном рабочем процессе есть переменная conditionstring, ее и станем сравнивать с нашим полем.

Плюсы такого решения:
+ достаточно один раз сделать узел-болванку, и превращение его в рабочий узел для автоматизации конкретного бизнес-процесса в последующем займёт совсем немного времени
+ очень легко менять траекторию движения заказа - не нужно менять сам рабочий процесс, достаточно изменить справочные значения в tasklist и logicoftransitions.
+ с условием небольшой доработки можно реализовать возможность для  пользователя перескакивать в произвольный шаг, или даже назначать отдельным заказам персональную траекторию.

Минусы:
- нельзя поглядеть на красивой диаграмке, где в данный момент находится наш заказ, как это можно сделать, если использовать штатный линейный workflow

Для того, чтобы хотя бы частично нивелировать этот недостаток, можно отображать в DispForm - форме просмотра Заказа все связанные задачи. Это даст наглядную картину того, на каком шаге в данный момент находится заказ и от какого пользователя(ей) ожидаются данные. Делается это просто: мы хотим, чтобы задачи были привязаны к заказу - достаточно присвоить полю-подстановке "Заказ” значение ИД текущего элемента. Несложно также отобразить их в форме просмотра Заказа, нужно просто на DispForm для списка “Заказы” добавить вебчасть - форму просмотра с фильтром, который отображает элементы списка задач со значением поля-подстановки:ИД равным параметру взятому из строки запроса браузера.

Создание Заказа, привязанного к Регламенту. Автозаполнение и скрытие полей.

Как следует из ТЗ, необходимо иметь возможность создавать Заказы на изменение и удаление Регламента. Для этого, очевидно, Заказ должен быть привязан к Регламенту.
Пожеланием заказчика системы было автозаполнение полей в этих вновь создаваемых заказах – поля из Регламента должны были переноситься в Заказ.
Одним из испробованных способов было создание готового Заказа рабочим процессом, и отправка пользователю ссылки на получившийся Заказ. Однако эта практика показала свою неэффективность – пользователю не хочется отвлекаться на почту, ему хочется видеть создаваемый Заказ на экране с возможностью внесения изменений ещё до его создания.
Было придумано вот такое решение. Используем всё тот же Sharepoint Designer. Создаётся новая форма для просмотра элемента списка Регламенты, причём создавать её желательно клонированием существующего файла DispForm.aspx
Затем под формой, отображающей все поля, создаём форму создания нового элемента списка Заказы. Таким образом, мы имеем одну форму, в верхней части которой находятся поля в режиме просмотра, в нижней - в режиме заполнения.
Для заполнения используем старый добрый джаваскрипт. Код скрипта можно разместить на нашей *.aspx - странице после строки:
 
<asp:Content ContentPlaceHolderId="PlaceHolderMain" runat="server">

или в новой веб-части Редактор контента, размещенной под всеми остальными веб-частями.

Шаг номер один: взять значение из полей верхней веб-части. Нюанс в том, что у этих полей совпадающие id. Так, например, если у нас несколько полей с типом “Однострочный текст” - у всех у них будет id “SPFieldText” Это конечно же затрудняет поиск нужного значения, и вот один из способов решения этой задачи:
   //Получаем содержимое всех тэгов TD
   var inputTags = document.getElementsByTagName('TD');
  
   //Создаём массивы для хранения содержимого различных типов полей
   al = new Array();
   nm = new Array();
   lp = new Array();
   bl = new Array();
   ch = new Array();
  
   //Заполняем массивы значениями
   for(var i=0;i<inputTags.length;i++)
   if(inputTags[i].id == 'SPFieldNumber')
       al.push(inputTags[i]);
   else if(inputTags[i].id == 'SPFieldText')
       nm.push(inputTags[i]);
   else if(inputTags[i].id == 'SPFieldLookup')
       lp.push(inputTags[i]);
   else if(inputTags[i].id == 'SPFieldBoolean')
       bl.push(inputTags[i]);
   else if(inputTags[i].id == 'SPFieldChoice')
       ch.push(inputTags[i]);

Теперь у нас в элементе массива al[0] - содержимое первого тэга с id “SPFieldNumber”, в al[1] - второго и т.д...

Шаг номер два: записать полученные значения в соответствующие поля. В форме редактирования, в отличие от формы просмотра, все поля имеют уникальный id, (кстати, его можно посмотреть в IE кликнув F12, затем выбрав нужное поле курсором - поиском):

Capture5

Для каждого типа поля требуется свой индивидуальный подход, например для полей с типом “Однострочный текст” и “Выбор” содержимое тега выглядит вот так:

<td valign="top" class="ms-formbody" width="450px" id="SPFieldText">
        <!-- FieldName="Название"
             FieldInternalName="Title"
             FieldType="SPFieldText"
          -->
            Новая РП
        </td>
и значения из содержимого тега можно взять следующим образом:

//запишем в переменную содержимое тега
nm0=nm[0].innerHTML;

//зададим переменную для определения закрывающего символа комментария, после которого и находится требуемый нами текст
end_comment = '-->';

//возьмем только нужные символы
end_pos = nm0.indexOf( end_comment );
nm0=nm0.substr( end_pos + end_comment.length );
nm0 = nm0.replace("&nbsp;","");

//запишем текст в соответствующее поле
document.getElementById("ctl00_m_g_41362970_e9f4_429e_b60f_f62d1e52e332_ff11_new_ctl00_ctl00_TextField").value = nm0

Для поля с типом “Подстановка”, содержимое тега выглядит так

<td valign="top" class="ms-formbody" width="450px" id="SPFieldLookup">
        <!-- FieldName="МР"
             FieldInternalName="_x0418__x043d__x0441__x0442__x04"
             FieldType="SPFieldLookup"
          -->
            <a href="/it/d/dpp/nmc/Lists/InstanceForis/DispForm.aspx?ID=6&RootFolder=*">МР Сибирь​</a>
        </td>
нам необходимо только значение ID, получить его можно, используя регулярные выражения, вот так:

lp0 = lp[0].innerHTML;
   var r = /ID=(\d+)/ig
   if ( r.test( lp0 ) )
    {
        res = lp0.match( r )
        lp0 = RegExp.$1
    }

Теперь нужно установить поле “Подстановка” в нужное значение - тут довольно объемный код, используется несколько функций, взятых вот отсюда: http://blogs.msdn.com/b/sharepointdesigner/archive/2007/06/13/using-javascript-to-manipulate-a-list-form-field.aspx
Эти функции копируются на нашу страницу как есть, без изменений. После этого для того чтобы установить поле “Подстановка” в значение достаточно написать, например:
setLookupFromFieldName("Команда заказавшая РП", lp0); // подставляет значение переменной lp0
или
setLookupFromFieldName("Связанная РП", vals["ID"]); // подставляет значение, полученное из адресной строки браузера
Таким образом, наш Заказ становится привязанным к Регламенту.

Иногда некоторые поля требуется установить в нужные значения явно. Например, для поля “Да/Нет”:
document.getElementById("ctl00_m_g_41362970_e9f4_429e_b60f_f62d1e52e332_ff3_new_ctl00_ctl00_BooleanField").checked=false;


Далее, некоторые поля нужно скрыть от пользователя, дабы избежать изменения пользователем. В моём случае это поля “Связанный регламент” и “Тип заказа”.
Для скрытия полей нам полей существует отличное решение в библиотеке jQuery. Достаточно подключить эти библиотеки, указав путь к ним следующим образом:
<script language="javascript" type="text/javascript" src="/jQuery%20Libraries/jquery-1.6.1.min.js"></script>

и теперь для того, чтобы скрыть строку от пользователя, достаточно вот такой конструкции:

$('#ctl00_m_g_41362970_e9f4_429e_b60f_f62d1e52e332_ff24_new_ctl00_Lookup').closest('tr').hide();

Было бы неправильно не упомянуть здесь про SPServices - библиотеку на базе jQuery, написанную специально для Sharepoint. В этом проекте она не пригодилась, но тем не менее там очень много полезных функций. Например $().SPServices.SPCascadeDropdowns - позволяет фильтровать выпадающие значения поля “Подстановка” в зависимости от выбора в предыдущих полях и ещё множество других полезностей. Адрес проекта: http://spservices.codeplex.com

Проверка введённых полей

В SharePoint есть признак обязательности/необязательности поля и возможность задать формулу для проверки вводимых значений.
Иногда штатного механизма проверки полей оказываетя недостаточно. Например, если нужно сделать вводимое значение зависимым от других значений.
Для того, чтобы реализовать проверку полей, можно использовать возможности решения SharePoint Power EventReceiver 2010 взятого отсюда http://ilovesharepoint.codeplex.com/releases/view/55733
Установленное решение добавляет в параметры списка 2 дополнительных пункта меню:

Capture6

и позволяет написать код на powershell для любого обработчика событий. В случае, когда нам нужно проверить корректность вводимых значений, нам нужно определить функции ItemAdding и ItemUpdating
Для этого просто раскомментариваем их и пишем код вроде этого:

function ItemUpdating{
   if( $properties.AfterProperties["_x0413__x0440__x0443__x043f__x04"] -eq 0 ) { $atmsg="Команда заказавшая РП; "
       $warcnt=$warcnt+1
}
  if(($properties.AfterProperties["xpress"] -eq "true") -and ($warcnt))
{
$properties.ErrorMessage = "<b>Вам необходимо заполнить обязательные поля:</b><br>$atmsg<br>Нажмите Назад в браузере для возврата на форму заполнения)"
$properties.Cancel = $true
}
}

Будьте внимательны  с коварными Before- и AfterProperties, подробно про них можно почитать тут: http://gandjustas.blogspot.com/2011/05/blog-post.html

Ограничение доступа на закрытие задачи, оповещения о просроченной задаче.

Изначально в свежесозданном списке Задачи можно назначать задачу только одному человеку – однако это легко изменить, просто поменяв свойство “Может содерать несколько значений” для поля “Кому назначено”

Сложнее решается другой момент – как позволять изменять задачу только тем пользователям, которым она непосредственно назначена? В SharePoint Designer есть активность “Дать разрешения на элемент списка” – она доступна в так называемом “Шаге олицетворения”, в котором все активности выполняются от имени создателя рабочего процесса. Однако эта активность не работает с полем, в котором у нас несколько пользователей – работает только для одного.

Здесь можно воспользоваться ещё одним решением от создателя Power EventReceiver 2010 – оно называется Advanced Workflow Actions for SharePoint Designer 2010, находится по ссылке http://ilovesharepoint.codeplex.com/wikipage?title=Workflow%20Actions%20for%20SharePoint%20Designer%202010&referringTitle=Documentation и позволяет выполнять дополнительные активности в нашем рабочем процессе. Нас интересует активность Execute PowerShell Script – воспользуемся ей, чтобы разделить содержимое  поля “Кому назначено” на составляющие – на отдельных пользователей.

Вот как выглядит активность в workflow (запускаем на создании новой задачи):

Capture7

А вот её код, достаточно простой благодаря powershell:

Capture8

Понятно, ограничение этого решения - максимум 5 пользователей. Обычно этого более чем достаточно.

Наконец, как организовать оповещение пользователя об истечении срока по задаче (или о приближении этого срока, или о том что задача истекла 2 дня назад – или обо всем вместе) – необходим ещё один рабочий процесс с активностью “Сделать паузу до…”, он будет ждать наступления нужного срока и отправлять оповещение.

Редирект на нужную страницу

Время от времени требуется перенаправить пользователя на какую-либо страницу по нажатию кнопки “Сохранить”.

Если это всегда одна и та же ссылка, то достаточно удалить стандартную кнопку и создать свою с кодом наподобие этого:

<input type="button" value="Сохранить" name="btnFormAction" onclick="javascript: {ddwrt:GenFireServerEvent('__commit;__redirect={../orderz/myorders.aspx}')}" />

Если же направление редиректа зависит от выбранных полей, то поможет всё тот же Power EventReceiver 2010. Код примерно такой:

function ItemAdding{

   if ($properties.AfterProperties["reqtype"] -eq "Создание РП")
    {
$properties.Status = [Microsoft.SharePoint.SPEventReceiverStatus]::CancelWithRedirectUrl
$properties.RedirectUrl = "../orderz/NewForm.aspx"
}
   else
{
$properties.Status = [Microsoft.SharePoint.SPEventReceiverStatus]::CancelWithRedirectUrl
$properties.RedirectUrl = "../regproc"
}  
}

Ограничение доступа на создание/изменение без ограничения разрешений

Пользователю запрещено создавать, изменять или удалять элементы в списке Регламенты, однако если лишить его разрешений на выполнение этих действий, то и рабочему процессу не удастся проделать эти операции (если конечно не выполнять эти действия в Шаге олицетворения – но в этом случае автором будет создатель, что не всегда удобно)

Одно из решений – можно попросту сделать новые формы для списка – вместо традиционных NewForm, EditForm создать новые, поставить на них галочку “по умолчанию” и удалить оттуда все поля для редактирования, заменив их на какой-нибудь текст, объясняющий пользователю почему создавать/удалять Регламенты напрямую нельзя и что нужно сделать вместо этого – со ссылками и инструкциями.

Заключение

Конечно, для окончательной сдачи проекта заказчику требуются ещё некоторые “полировочные” действия – скрытие некоторых полей, создание пользовательских рабочих мест с настройкой представлений и меню в зависимости от роли в процессе. Однако основные “хитрости” в статье я перечислил, очень надеюсь на то что они кому-нибудь окажутся полезны.

воскресенье, 19 декабря 2010 г.

Хорошая статья о разработке приложений на SP

http://office.microsoft.com/ru-ru/sharepoint-designer-help/HA010239046.aspx

Действительно, в шаблонах от Microsoft еть любопытные вещи и тут описывается хотя бы примерно “как это работает”

И также полезно вот это, как памятка заказчику перед началом любого проекта. Прежде чем начинать, определи следующие понятия:

* Исполнители и роли в бизнес-процессе.    В данном случае ответственный за проект создает проект и следит за данными о задачах, вопросах и т. д. Ответственным за задачи назначаются вопросы и задачи, с которыми им необходимо взаимодействовать для выполнения своих заданий. Управляющие должны видеть сводные данные общего состояния проекта.
* Элементы пользовательского интерфейса, необходимые для различных исполнителей    В данном случае у ответственного за проект, ответственных за задачи и управляющего свои режимы отображения, соответствующие выполняемым ими задачам. Например, у ответственного за задачу должна быть возможность просмотра всех вопросов, назначенных ему, тогда как у ответственного за проект должна быть возможность просмотра всех просроченных вопросов.
* Суть бизнес-процесса    В данном случае ответственный за проект создает проект, промежуточные отчеты, задачи и бюджетные операции, а затем следит за выполнением проекта. Ответственный за проект постоянно осуществляет доступ ко всем данным, а ответственный за задачу должен работать над данными при назначении ему задачи.
* Местонахождение данных     Будут использоваться только данные в Windows SharePoint Services или требуется доступ к внешним данным (из базы данных, через веб-службу, через каталог бизнес-данных и т. д.), потребуется ли хранение данных вне Windows SharePoint Services?
* Отношения между данными    В данном случае требуются проект, отчет, задачи и вопросы. Они должны составлять логическую иерархию. Также в иерархию должны входить пользователи и точки данных и такие элементы, как бюджет, количество дней и им подобные.

Вроде бы и так всё очевидно? Неа, не всегда…

На что ещё способен MOSS2007…

 

Последнее небольшое приложение использует по-максимуму всё то, что можно сделать не открывая VisualStudio =)

Приложение – опрос для экспертов. Стандартный в целом опрос, с одной только особенностью – в процессе него подсчитываются баллы и записываются в отдельный список. Для этого потребовалось с помощью Power Item Event Receiver добавить powershell-код в события ItemUpdating/ItemAdding. “Малой кровью”, а именно рабочими процессами – никак, т.к. на элементы опроса рабочие процессы попросту не навешиваются – недоработка.

DispForm того списка, куда пишутся результаты, максимально облегчена – с помощью style.display="none"; скрыто всё лишнее – меню добавления/удаления, кнопки, информация о создании – всё.

Часть данных хранится в другом списке, отображается в настоящей DispForm с помощью связки через переменную в адресной строке:

image
Создаем новый элемент в связанном списке

image
С помощью переменной в строке URL делаем привязку. Главное, чтобы пользователь её не поменял =)

В целом страница-анкета (так DispForm называется теперь на языке заказчика) выглядит вполне компактно:

image

Заметил интересную особенность: если в ссылке на страницу, где следует выполнить какое-то действие <a href …> присутствует “onclick="javascript:this.href = unescapeProperly(escape(this.href)); GoToLink(this); return false;" target="_self" то после окончания действия (нажатия ОК или Отмена) нас вернет на исходную страницу. Удобно, но загвоздка в одном - если у исходной страницы в URL присутствует Source – то идет возврат на него.

Помог вот этот топик форума: http://www.gotdotnet.ru/forums/5/119622/

“В SharePoint есть уже встроенный javascript, для организации передачи Source. Так что надо просто его использовать. Для этого оформляйте ссылку так:

<a href="url" onclick="javascript:GoToLink(this); return false;" target="_self">Link title</a>

Только чтобы это заработало необходимо, чтобы на странице было описание javascript функции GetSource, которая почему-то не описана в файлах javascript'ов SharePoint. Так что Вам придется на страницу добавить еще и вот такой кусок:

<SCRIPT type="text/javascript">
          function GetSource(defaultSource)
          {
          return escapeProperly(STSPageUrlValidation(window.location.href));
          }
</SCRIPT>”

вторник, 10 августа 2010 г.

Ошибка "Эта задача сейчас заблокирована выполняющимся рабочим процессом и не может быть изменена"

Создан рабочий процесс (workflow) в редакторе Sharepoint Designer
У этого РП есть активность (activity) "Получить данные от пользователя", в процессе выполнения которой для пользователя создается задача.

Как правило, если пользователь завершает задачу, данные передаются в рабочий процесс, задача переходит в статус завершенных и всё отлично работает.
Однако иногда после нажатия на "Завершить задачу" форма закрывается, но задача не завершается, а остается в статусе "не начата"

Если попытаться повторно нажать на "Завершить задачу" - то выдается вот такая ошибка:
Эта задача сейчас заблокирована выполняющимся рабочим процессом и не может быть изменена

Когда задача уже "застряла" - невозможно сделать ничего, кроме как завершить рабочий процесс принудительно, и начать всё сначала. Но и при этом с большой долей вероятности очередная созданная задача опять застрянет.

В сети не нашел вообще ничего об этой ошибке, кроме разве что: http://www.gotdotnet.ru/forums/5/128821/

Завел кейс в поддержке.
Условие закрытия кейса - задача после нажатия "Завершить задачу" должна в 100% случаях завершаться.

ISSUE
You created a workflow in SharePoint Designer. It has activity "Collect user feedback", a SharePoint task is creating during this activity.

Usually, when user finish a task, the data is transferred successfully and then the task gets the status "completed".
However, sometimes after user clicks on "Finish task" button the form closes, but task still have status "not started".
Any following attempt to "finish task" causes the error "this task has been blocked by workflow and can't be changed" (or "This task is currently locked by a running workflow and cannot be edited")

The only workaround found is to finish the workflow by hand and start the workflow from the beginning.

AGREED RESOLUTION
We can close the case when the specific activity ("Collect Feedback") will complete the associated task.
We can also close the case if we can explain why should we have the behavior and/or we will provide a workaround.

CURRENT STATUS
We reviewed the issue. Collected some data and established the logs we will need.

ACTION PLAN
You need to reproduce the issue:
- When a user tries to complete the task
- When he tries again and receives the error
You will collect the following data:
- The SharePoint logs when the issue was observed
- The workflow logs
- The time(s) when the issue(s) occurred
- The permissions of the user completing the task

Были собраны логи SharePoint и WorkFlow (последнее - с помощью этого способа)

По результатам рассмотрения кейса инженером было выдано предварительное решение: это баг, такое случается периодически и фикса на этот баг нет. Однако можно лечить проявления с помощью скрипта, вот пример реализации на повершелл:
[System.Reflection.Assembly]::LoadWithPartialName(”Microsoft.SharePoint”)

function ChangeWorkflowVersionOnItem
{
param ($siteurl, $listURL, $itemID)

Write-Host "Getting data"
$mysite = new-object Microsoft.SharePoint.SPSite($siteurl)
$myweb = $mysite.OpenWeb()
$mylist = $myweb.GetList($listURL)
$myitem = $mylist.GetItemById($itemID)
$myitem["WorkflowVersion"] = 1
$myitem.SystemUpdate()
Write-Host "DONE Item ", $itemID
}

function ChangeWorkflowVersionOnList
{
param ($siteurl, $listURL)

Write-Host "Getting data"
$mysite = new-object Microsoft.SharePoint.SPSite($siteurl)
$myweb = $mysite.OpenWeb()
$mylist = $myweb.GetList($listURL)
foreach ($item in $mylist.Items)
{
if ($item["WorkflowVersion"] -ne 1)
{
$item["WorkflowVersion"] = 1
$item.SystemUpdate()
Write-Host "DONE Item ", $item.ID
}
}
Write-Host "DONE List"
}

# to call the functions use the commands as below:
# For a specific item (recommended)
#ChangeWorkflowVersion "http://an-moss2007dev" "Lists/Tasks" 2
# or for an entire list
#ChangeWorkflowVersionOnList "http://an-moss2007dev" "Lists/Tasks"
Продолжение следует, пока что жду доп инфо от MS, а тем временем моя задача придумать как сделать так чтобы всё это выполнялось в автоматическом режиме и заказчик был доволен...

PS Статья о проблеме есть ещё вот тут: http://blogs.code-counsel.net/Wouter/Lists/Posts/Post.aspx?ID=118, но там всё сводится к фразе "DO NOT UPGRADE UNTIL ALL RUNNING WORKFLOWS ARE COMPLETE" т.к. рассматривается случай, когда разработчик изменяет РП в тот момент, когда этот РП запущен и задача назначена. Однако, в нашем случае в момент редактирования РП задачи были завершены, и инстансы этого РП не были запущены.

UPDATE В действительности проблема решалась совсем иначе. В какой-то момент была выявлена закономерность: задача зависает, если заходить по URL xxxx.xxx.ru и не зависает, если заходить по yyyyy:
Оба эти URL вели на один и тот же сайт - это было реализовано через сопоставления для альтернативного доступа. Однако было реализовано некорректно - не было создано отдельного веб-сайта IIS для xxxx.xxx.ru
Что сделано: веб-приложение корректно расширено, xxxx.xxx.ru создано на порту 80 (URL: xxxx.xxx.ru:80)
После этого проблема ушла.

UPDATE2 А вообще ничо не стоит взять и поломать это снова. Один из простых способов: поменять в параметрах поля "Кому назначено" списка Задач пункт "Разрешить назначать нескольким" с Нет на Да... . Наверняка любую другую фигнюшку тоже достаточно поменять, чтобы посыпалось всё нафиг... вот так-то...

Проблема с email-рассылкой в MOSS2007

Не так давно мы имели проблему с рассылкой у пользователей на различных веб-приложениях. Полностью проблема была сформулирована мною так: "В новом веб-приложении не работают оповещения, а также нет автоматического запуска рабочих процессов"
Для корректной работы оповещений помогло вот это:
Есть информация на technet:
1.Открыть консоль (Пуск – Выполнить… – cmd – Ок)
2.Запускаем программу администрирования stsadm.exe (пишем stsadm.exe и нажимаем Enter), смотрим, что она у вас есть и радуемся ее наличию.
3.Пишем: stsadm -o setproperty -url http://site -pn alerts-enabled -pv «true» (этой командой мы включаем оповещения)
4.Теперь задаем параметры: stsadm -o setproperty -url http://site -pn job-immediate-alerts -pv «every 5 minutes».
После того как согласно описанию на ресурсе http://technet.microsoft.com/en-us/library/cc263206.aspx было произведено выключение-включение механизма оповещения и изменение времени проверки оповещений, которые должны быть отработаны: http://technet.microsoft.com/en-us/library/cc262432.aspx - всё заработало.

Для запуска WorkFlow помогло вот это:
C:\>stsadm -o getproperty -pn job-workflow -url http://site/

C:\>stsadm -o setproperty -pn job-workflow -pv "Every 2 minutes" -url http://site/
Операция успешно завершена.
C:\>stsadm -o getproperty -pn job-workflow -url http://site/

C:\> setproperty -pn job-workflow -pv "Every 2 minutes between 0 and 59" -url http://site/
Операция успешно завершена.
C:\>stsadm -o getproperty -pn job-workflow -url http://site/


В один прекрасный день всё слетело и это также перестало работать, приведенные выше решения не работали. Пришлось задействовать ТП Microsoft. После анализа таблиц в БД SharePointа оказалось, что у нас был ещё один сервер приложений, на котором не был настроен smtp-протокол, однако он хватал сообщения на отправку, после чего писал в лог ошибку и письмо благополучно не уходило. Вот этим запросом можно было увидеть, какой сервер используется в настоящий момент: SELECT * FROM [WSS_Content_20101].[dbo].[TimerLock]

∙ Симптомы - Customer noticed that only the greeting email arrives for some immediate alerts. He found out that the scope is web application. Immediate alerts work only for one.
∙ Причина - The recently added Sharepoint server could not send emails. Content databases whose timer jobs were linked to this server ran into this problem.
∙ Решение - When we stopped the timer service on the new ser, after timerlock changed for the dtabases, emails went out for immediate alerts started to work.
∙ Дополнительная информация - possibly email receiving restrictions on the Lotues Notes side

понедельник, 26 июля 2010 г.

Сохранение рабочего процесса...

Озадачился тем как сохранить рабочий процесс из SharePoint Designer на случай непредвиденного повреждения/удаления.
Задал вопрос на форуме, пока ничего... http://www.gotdotnet.ru/forums/5/131116/616305/#post616305

UPDATE на самом деле, рабочий процесс сохранить можно только вместе с содержимым, к которому он собственнно привязан - списку, библиотеке. Ну а учитывая, что сам рабочий процесс это тоже элемент библиотеки рабочих процессов - то и сохранять надо весь узел.
Как я понял, SharePoint 2007 предлагает 3 основных способа сохранить/восстановить содержимое узла, условно назовем их:
1. Site/List Template (из меню "Параметры", сохранить как шаблон с включением содержимого, создаётся stp файл)
2. export/import (с помощью командной строки stsadm или SharePoint Designerа, создается cmp файл)
3. backup/restore

Второй способ - заведомо не подходит, microsoft пишет: "If I understood well the question, I could already tell you that exporting sites where we have workflows then using them again will not work.
The reason is that when using export/import, the lists/elements identifiers are generated again and the workflows are not able to identify what is the specific object he needs to work with." Т.е. мы конечно можем сохранить/восстановить контент, но РП придется заново переделывать, перепривязать все поля к нашим активностям заново.

Третий способ - http://technet.microsoft.com/en-us/library/dd819893(office.12).aspx - Microsoft Press: Backup and restore. Ещё предстоит попробовать, пока вот что написал инженер MS: "I could also ad that backup/restore operation do keep the element identities, and the workflow might work in the restored sites.
However, you cannot use a restored site in the same farm, because you might have a conflict of IDs."

Наконец, первый способ, который протестирован мною, и вроде бы пока работает:
"Now, if we get back to the limitation, there is a way to change it, by changing the max-template-document-size property for the site.
Please check the following resources:
- http://msdn.microsoft.com/en-us/library/bb802960(office.12).aspx - Site Template
- http://support.microsoft.com/kb/960969 - Error message when you try to save a site as a template in SharePoint Server 2007: "Failure decompressing data from a cabinet file"
Первая ссылка описывает изменение параметра в строке stsadm, который позволяет увеличить размер шаблона. А во второй - упомянута проблема, с которой мы можем столкнуться при попытке использования первой. Там же можно встретить фразу "Templates are not best practice for site level backup/restore operations"... Что ж, это понятно, но пока - это единственный приемлемый способ сохранить рабочий процесс в MOSS2007...


PS на самом деле у каждого из способов своя область применения, свои особенности, если ещё решения сторонних разработчиков - подробней есть в документации и например вот тут в статье Нелли Сатретдиновой "Инструменты миграции SharePoint на все случаи жизни": http://doc.elcat.kg/Misc/SOMag/content/2008/samag_04_65/samag4(65)-27-33.pdf

PPS Here is a summary of the case:

ACTION/RESULT
You were interested on how a site can be saved as template then use it along with the workflows created on it.
You were also concerned about the limitation on the size of the files saved by SharePoint when saving as template.

CAUSE
Advisory case

RESOLUTION
Provided answers:
- Exporting sites where we have workflows then using them again will not work.
The reason is that when using export/import, the lists/elements identifiers are generated again and the workflows are not able to identify what is the specific object he needs to work with.
- About the limitation, there is a way to change it, by changing the max-template-document-size property for the site
Additional resources:
- http://msdn.microsoft.com/en-us/library/bb802960(office.12).aspx - Site Template
- http://technet.microsoft.com/en-us/library/dd819893(office.12).aspx - Microsoft Press: Backup and restore
- http://support.microsoft.com/kb/960969 - Error message when you try to save a site as a template in SharePoint Server 2007: "Failure decompressing data from a cabinet file"

More info:
I could also ad that backup/restore operation do keep the element identities, and the workflow might work in the restored sites.
However, you cannot use a restored site in the same farm, because you might have a conflict of IDs.