GrabDuck

Создание workflow. Продолжение | www.ossportal.ru

:

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

Для начала нужно определиться, что конкретно потребуется сделать для реализации заявленного функционала. Нам понадобится новое свойство, в котором будем хранить хранить ответ пользователя на вопрос "Присылать уведомления?". Это свойство мы добавим к start-task:

<!-- Свойства -->
<properties>
  <!-- Присылать письма -->
  <property name="tcwf:notifyMe">
    <type>d:boolean</type>
    <default>false</default>
  </property>
</properties>

 Также дадим возможность редактору и корректоу писать комментарии к статье по ходу рассмотрения, для этого добавим новый аспект к CorrectTask, EditTask и ReviseTask(чтобы и пользователь мог прочитать эти комментарии):

<!-- Комментарий -->
<aspect>tcwf:commentAspect</aspect>

Ну и добавим описание нового аспекта:

<aspect name="tcwf:commentAspect">
  <title>TrashCo commentable aspect</title>
  <properties>
    <property name="tcwf:comments">
      <type>d:text</type>
      <mandatory>false</mandatory>
      <multiple>false</multiple>
    </property>
  </properties>
</aspect>

 Таким образом, модель данных теперь выглядит вот так (extension/tcWorkflowModel.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!-- Описание модели бизнес-процесса -->
<model name="tcwf:workflowmodel"  xmlns="http://www.alfresco.org/model/dictionary/1.0">
 
  <!-- Необязательные метаданные модели -->
  <description>TrashCo Workflow Model</description>
  <author>lx</author>
  <version>0.0</version>
 
  <!-- Импорт необходимых описаний -->
  <imports>
    <import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d" />
    <import uri="http://www.alfresco.org/model/bpm/1.0" prefix="bpm" />
  </imports>
 
  <!-- Описываем пространство имен для нашего процесса -->
  <namespaces>
    <namespace uri="http://www.trashco.com/model/workflow/1.0"  prefix="tcwf" />
  </namespaces>
 
  <types>
 
    <!-- Подача статьи -->
    <type name="tcwf:submitTask">
      <!-- Наследуем от bpm:startTask -->
      <parent>bpm:startTask</parent>
      <!-- Свойства -->
      <properties>
        <!-- Присылать письма -->
        <property name="tcwf:notifyMe">
          <type>d:boolean</type>
          <default>false</default>
        </property>
      </properties>
      <!-- Указываем обязательные аспекты -->
      <mandatory-aspects>
        <!-- Редактор -->
        <aspect>bpm:assignee</aspect>
        <!-- Статья может быть опубликована online -->
        <aspect>tcwf:webable</aspect>
      </mandatory-aspects>
    </type>
 
    <!-- Корректирование статьи -->
    <type name="tcwf:correctTask">
      <!-- Наследуем от bpm:workflowTask -->
      <parent>bpm:workflowTask</parent>
      <!-- Документ может быть отредактирован -->
      <overrides>
        <property name="bpm:packageItemActionGroup">
          <default>edit_package_item_actions</default>
        </property>
      </overrides>
      <mandatory-aspects>
        <!-- Статья может быть опубликована online -->
        <aspect>tcwf:webable</aspect>
        <!-- Комментарий -->
        <aspect>tcwf:commentAspect</aspect>
      </mandatory-aspects>
    </type>
 
    <!-- Техническое редактирование статьи -->
    <type name="tcwf:editTask">
      <!-- Наследуем от bpm:workflowTask -->
      <parent>bpm:workflowTask</parent>
      <!-- Документ может быть отредактирован -->
      <overrides>
        <property name="bpm:packageItemActionGroup">
          <default>edit_package_item_actions</default>
        </property>
      </overrides>
      <mandatory-aspects>
        <!-- Статья может быть опубликована online -->
        <aspect>tcwf:webable</aspect>
        <!-- Комментарий -->
        <aspect>tcwf:commentAspect</aspect>
      </mandatory-aspects>
    </type>
 
    <!-- Доработка статьи -->
    <type name="tcwf:reviseTask">
      <!-- Наследуем от bpm:workflowTask -->
      <parent>bpm:workflowTask</parent>
      <!-- Документ может быть отредактирован -->
      <overrides>
        <property name="bpm:packageItemActionGroup">
          <default>edit_package_item_actions</default>
        </property>
      </overrides>
      <mandatory-aspects>
        <!-- Статья может быть опубликована online -->
        <aspect>tcwf:webable</aspect>
        <!-- Комментарий -->
        <aspect>tcwf:commentAspect</aspect>
      </mandatory-aspects>
    </type>
 
    <!-- Публикация статьи -->
    <type name="tcwf:publishTask">
      <!-- Наследуем от bpm:workflowTask -->
      <parent>bpm:workflowTask</parent>
      <!-- Документ может быть только просмотрен -->
      <overrides>
        <property name="bpm:packageItemActionGroup">
          <default>read_package_item_actions</default>
        </property>
      </overrides>
      <mandatory-aspects>
        <!-- Статья может быть опубликована online -->
        <aspect>tcwf:webable</aspect>
      </mandatory-aspects>
    </type>  
 
  </types>
 
 
  <!-- Описываем аспекты, которые будем использовать -->
  <aspects>
    <!-- Статью можно опубликовать online -->
    <aspect name="tcwf:webable">
      <title>TrashCo webable aspect</title>
      <properties>
        <property name="tcwf:publishOnline">
          <type>d:boolean</type>
          <mandatory>true</mandatory>
          <multiple>false</multiple>
        </property>
      </properties>
    </aspect>
    <aspect name="tcwf:commentAspect">
      <title>TrashCo commentable aspect</title>
      <properties>
        <property name="tcwf:comments">
          <type>d:text</type>
          <mandatory>false</mandatory>
          <multiple>false</multiple>
        </property>
      </properties>
    </aspect>
  </aspects>
</model>
 

Теперь добавим в бизнес-процесс код, отвечающий за отправку уведомлений. Внутри javascript-кода переменные, объявленные в модели доступны в виде <префикс>_<имя> (к примеру, tcwf:notifyMe доступна как tcwf_notifyMe). Для отправки писем (как и для многих других вещей) в Alfresco используется механизм действий (actions). Вот таким образом нужно изменить CorrectTask, чтобы инициатор стал получать письма:

<task-node name="CorrectTask">
  <!-- Задача для корректора -->
  <task name="tcwf:correctTask" swimlane="corrector" />
  <!-- Одобрить статью -->
  <transition name="approve" to="EditTask">
    <action class="org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript">
      <!-- Отправляем уведомление -->
      <script>
        <![CDATA[
          logger.log("PublishPaperProcess: Approved");
          if (tcwf_notifyMe)
          {
            var mail = actions.create("mail");
            mail.parameters.to = initiator.properties["cm:email"];
            mail.parameters.subject = "Публикация статьи";
            mail.parameters.text = "Добрый день.nnВаша статья прошла проверку редактора и отправлена техническому редактору на рассмотрение.";
            mail.execute(bpm_package);
            }
          ]]>
      </script>
    </action>
  </transition>
  <!-- Отклонить статью -->
  <transition name="reject" to="ReviseTask">
    <action class="org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript">
      <!-- Отправляем уведомление -->
      <script>
        <![CDATA[
          logger.log("PublishPaperProcess: Rejected");
          if (tcwf_notifyMe)
          {
            var mail = actions.create("mail");
            mail.parameters.to = initiator.properties["cm:email"];
            mail.parameters.subject = "Публикация статьи";
            mail.parameters.text = "Добрый день.nnВаша статья не прошла проверку редактора и нуждается в доработке. nnКомментарии:n"+ tcwf_comments;
            mail.execute(bpm_package);
          }
          ]]>
      </script>
    </action>
  </transition>
</task-node>

Как видно из кода, делается это достаточно просто. Внутрь <transition> добавляется скрипт, который срабатывает при соответствующем переходе. Внутри скрипта мы проверяем, нужно ли отправлять письмо: если да, то заполняем соответствующие поля письма и отправляем его. Всё просто, но не совсем :) Есть один неприятный момент: Alfresco отправляет письма от имени того пользователя, который инициировал переход (на багзилле это значится как "expected behaviour, not a bug"). Таким образом, отсылать письма с отправителем вида noreply@somehost.zz становится затруднительно. Могу предложить такие методы решение:

  • использовать хорошо настроенный локальный почтовый сервер, который при отправке изменяет соответствующие поля
  • использовать внешний сервер (gmail.com, например), но в таком случае отправитель будет @gmail.com ,что тоже не очень хорошо
  • прописать всем пользователям системы одинаковый адрес email :)
  • написать свой класс для отправки почты

Таким образом, файл (extension/workflows/PublishPaperProcess.xml) с описанием бизнес-процесса теперь выглядит вот так:

<?xml version="1.0" encoding="UTF-8"?>
 
<!-- Начало описания процесса -->
<process-definition xmlns="urn:jbpm.org:jpdl-3.1" name="tcwf:publishpaper">
 
  <!-- Исполнитель "инициатор" -->
  <swimlane name="initiator" />
 
  <!-- Исполнитель "корректор", выбирается из группы correctors -->
  <swimlane name="corrector">
    <assignment class="org.alfresco.repo.workflow.jbpm.AlfrescoAssignment">
      <pooledactors>#{people.getGroup('GROUP_correctors')}</pooledactors>
    </assignment>
  </swimlane>
 
  <!-- Исполнитель "редактор", выбирается при создании  процесса -->
  <swimlane name="editor">
    <assignment class="org.alfresco.repo.workflow.jbpm.AlfrescoAssignment">
      <actor>#{bpm_assignee}</actor>
    </assignment>
  </swimlane>
 
  <!-- Исполнитель "издатель", выбирается из группы publishers -->
  <swimlane name="publisher">
    <assignment class="org.alfresco.repo.workflow.jbpm.AlfrescoAssignment">
      <pooledactors>#{people.getGroup('GROUP_publishers')}</pooledactors>
    </assignment>
  </swimlane>
 
  <!-- Начальный узел процесса -->
  <start-state name="Start">
    <!-- Задание для инициатора -->
    <task name="tcwf:submitTask" swimlane="initiator" />
    <!-- Подать статью на рассмотрение -->
    <transition name="" to="Submit" />
  </start-state>
 
  <!-- Подача статьи на рассмотрение -->
  <node name="Submit">
    <!-- Передать корректору -->
    <transition name="" to="CorrectTask"/>
  </node>
 
  <!-- Корректирование статьи -->
  <task-node name="CorrectTask">
    <!-- Задача для корректора -->
    <task name="tcwf:correctTask" swimlane="corrector" />
    <!-- Одобрить статью -->
    <transition name="approve" to="EditTask">
      <action class="org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript">
        <!-- Отправляем уведомление -->
        <script>
          <![CDATA[
            logger.log("PublishPaperProcess: Approved");
            if (tcwf_notifyMe)
            {
              var mail = actions.create("mail");
              mail.parameters.to = initiator.properties["cm:email"];
              mail.parameters.subject = "Публикация статьи";
              mail.parameters.text = "Добрый день.nnВаша статья прошла проверку редактора и отправлена техническому редактору на рассмотрение.";
              mail.execute(bpm_package);
              }
            ]]>
        </script>
      </action>
    </transition>
    <!-- Отклонить статью -->
    <transition name="reject" to="ReviseTask">
      <action class="org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript">
        <!-- Отправляем уведомление -->
        <script>
          <![CDATA[
            logger.log("PublishPaperProcess: Rejected");
            if (tcwf_notifyMe)
            {
              var mail = actions.create("mail");
              mail.parameters.to = initiator.properties["cm:email"];
              mail.parameters.subject = "Публикация статьи";
              mail.parameters.text = "Добрый день.nnВаша статья не прошла проверку редактора и нуждается в доработке. nnКомментарии:n"+ tcwf_comments;
              mail.execute(bpm_package);
            }
            ]]>
        </script>
      </action>
    </transition>
  </task-node>
 
  <!-- Техническое редактирование статьи -->
  <task-node name="EditTask">
    <!-- Задача для редактора -->
    <task name="tcwf:editTask" swimlane="editor" />
    <!-- Одобрить статью -->
    <transition name="approve" to="PublishTask">
      <action class="org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript">
        <!-- Отправляем уведомление -->
        <script>
          <![CDATA[
            logger.log("PublishPaperProcess: Approved2");
            if (tcwf_notifyMe)
            {
              var mail = actions.create("mail");
              mail.parameters.to = initiator.properties["cm:email"];
              mail.parameters.subject = "Публикация статьи";
              mail.parameters.text = "Добрый день.nnВаша статья прошла проверку технического редактора и отправлена на публикацию.";
              mail.execute(bpm_package);
            }
            ]]>
        </script>
      </action>
    </transition>
    <!-- Отклонить статью -->
    <transition name="reject" to="ReviseTask">
      <action class="org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript">
        <!-- Отправляем уведомление -->
        <script>
          <![CDATA[
            logger.log("PublishPaperProcess: Rejected2");
            if (tcwf_notifyMe)
            {
              var mail = actions.create("mail");
              mail.parameters.to = initiator.properties["cm:email"];
              mail.parameters.subject = "Публикация статьи";
              mail.parameters.text = "Добрый день.nnВаша статья не прошла проверку технического редактора и нуждается в доработке. nnКомментарии:n"+ tcwf_comments;
              mail.execute(bpm_package);
            }
            ]]>
        </script>
      </action>
    </transition>
  </task-node>
 
  <!-- Доработка статьи -->
  <task-node name="ReviseTask">
    <!-- Задача для инициатора -->
    <task name="tcwf:reviseTask" swimlane="initiator" />
    <!-- Подать статьи повторно -->
    <transition name="resubmit" to="Submit" />
    <!-- Отменить процесс публикации статьи -->
    <transition name="cancel" to="End" />
  </task-node>
 
 
  <!-- Публикация статьи -->
  <task-node name="PublishTask">
    <!-- Задача для издателя -->
    <task name="tcwf:publishTask" swimlane="publisher" />
    <!-- Конец процесса -->
    <transition name="done" to="End">
      <action class="org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript">
        <!-- Отправляем уведомление -->
        <script>
          <![CDATA[
            logger.log("PublishPaperProcess: Published");
            if (tcwf_notifyMe)
            {
              var mail = actions.create("mail");
              mail.parameters.to = initiator.properties["cm:email"];
              mail.parameters.subject = "Публикация статьи";
              mail.parameters.text = "Добрый день.nnВаша статья опубликована.";
              mail.execute(bpm_package);
            }
            ]]>
        </script>
      </action>
    </transition>
  </task-node>
 
  <!-- Последний узел -->
  <end-state name="End"/>
 
</process-definition>
<!-- Конец описания процесса -->

Если для отправки почты мы используем действия, то нужно настроить соответствующим образом Alfresco ( alfresco-global.properties и extension/custom-email-context.xml):

mail.host=localhost
#mail.username=
#mail.password=
mail.port=25
mail.protocol=smtp
mail.smtps.starttls.enable=false
mail.smtps.auth=false

 

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
 
<beans>
  <!-- Указываем, откуда брать параметры почтового сервера -->
  <bean id="mailService" class="org.springframework.mail.javamail.JavaMailSenderImpl">
    <property name="host">
        <value>${mail.host}</value>
    </property>
    <property name="port">
        <value>${mail.port}</value>
    </property>
    <property name="protocol">
        <value>${mail.protocol}</value>
    </property>
    <property name="username">
        <value>${mail.username}</value>
    </property>
    <property name="password">
        <value>${mail.password}</value>
    </property>
    <property name="defaultEncoding">
        <value>${mail.encoding}</value>
    </property>
    <property name="javaMailProperties">
        <props>
            <prop key="mail.smtps.auth">${mail.smtps.auth}</prop>
            <prop key="mail.smtps.starttls.enable">${mail.smtps.starttls.enable}</prop>
        </props>
    </property>
  </bean>
</beans>

После того, как код отправки уведомлений написан, нужно настроить пользовательский интерфейс. Ниже я привел конфигурационный файл web-extension/share-custom-config.xml, отвечающий за то, как этот бизнес-процесс будет выглядеть в Alfresco Share. В файле присутствуют минимальные комментарии, которых, как мне кажется, вполне достаточно для осознания происходящего :)

<!-- Настройки интерфейса -->
<alfresco-config>
  <!-- Отображение информации при просмотре бизнес-процесса -->
  <config evaluator="string-compare" condition="jbpm$tcwf:publishpaper">
    <forms>
      <form>
        <!-- Список видимых полей -->
        <field-visibility> 
          <show id="bpm:workflowDescription" />
          <show id="bpm:assignee"/>
          <show id="packageItems" />
          <show id="tcwf:publishOnline" />
          <show id="tcwf:notifyMe" />
        </field-visibility>
        <appearance>
          <!-- Блоки -->
          <set id="" appearance="title" label-id="workflow.set.general" />
          <set id="assignee" appearance="title" label-id="tcwf_assignee" />
          <set id="items" appearance="title" label-id="tcwf_items" />
          <!-- Отображение полей -->
          <field id="bpm:workflowDescription" label-id="tcwf_description">
            <control template="/org/alfresco/components/form/controls/textarea.ftl">
              <control-param name="style">width: 95%</control-param>
            </control>
          </field>
          <field id="tcwf:publishOnline" label-id="tcwf_publish_online" />
          <field id="tcwf:notifyMe" label-id="tcwf_notify_me" />
          <field id="bpm:assignee" label-id="tcwf_assign_to" set="assignee" />
          <field id="packageItems" set="items" label="" />
        </appearance>
      </form>
    </forms>
  </config>
  <!-- Подача статьи на рассмотрение -->
  <config evaluator="task-type" condition="tcwf:submitTask">
    <forms>
      <form id="workflow-details">
        <!-- Список видимых полей -->
        <field-visibility>
          <show id="tcwf:publishOnline" />
          <show id="packageItems" />
        </field-visibility>
        <appearance>
          <!-- Блоки -->
          <set id="" appearance="title" label-id="workflow.set.workflow.more_info" />
          <set id="items" appearance="title" label-id="tcwf_items" />
          <!-- Отображение полей -->
          <field id="tcwf:publishOnline" label-id="tcwf_publish_online"/>
          <field id="packageItems" set="items" label="tcwf_items_list"/>
        </appearance>
      </form>
    </forms>
  </config>
  <!-- Корректирование статьи -->
  <config evaluator="task-type" condition="tcwf:correctTask">
    <forms>
      <form>
        <!-- Список видимых полей -->
        <field-visibility>
          <show id="bpm:taskId" />
          <show id="bpm:status" />
          <show id="packageItems" />
          <show id="transitions" />
          <show id="tcwf:publishOnline"/>
          <show id="tcwf:comments"/>
        </field-visibility>
        <appearance>
          <!-- Блоки -->
          <set id="" appearance="title" label-id="workflow.set.task.info" />
          <set id="info" appearance="" template="/org/alfresco/components/form/3-column-set.ftl" />
          <set id="items" appearance="title" label-id="tcwf_items" />
          <set id="progress" appearance="title" label="" />
          <set id="response" appearance="title" label-id="workflow.set.response" />
          <!-- Отображение полей -->
          <field id="bpm:taskId" set="info" label-id="tcwf_task_id">
            <control template="/org/alfresco/components/form/controls/info.ftl" />
          </field>
          <field id="tcwf:comments" label-id="tcwf_comments" set="items"/>
          <field id="bpm:status" set="progress" label-id="tcwf_status"/>
          <field id="packageItems" set="items" label-id="tcwf_items_list"/>
          <field id="tcwf:publishOnline" label-id="tcwf_publish_online" set="items" read-only="true"/>
          <field id="transitions" set="response" />
        </appearance>
      </form>
    </forms>
  </config>
  <!-- Техническое редактирование статьи -->
  <config evaluator="task-type" condition="tcwf:editTask">
    <forms>
      <form>
        <!-- Список видимых полей -->
        <field-visibility>
          <show id="bpm:taskId" />
          <show id="bpm:status" />
          <show id="packageItems" />
          <show id="transitions" />
          <show id="tcwf:publishOnline"/>
          <show id="tcwf:comments"/>
        </field-visibility>
        <appearance>
          <!-- Блоки -->
          <set id="" appearance="title" label-id="workflow.set.task.info" />
          <set id="info" appearance="" template="/org/alfresco/components/form/3-column-set.ftl" />
          <set id="items" appearance="title" label-id="tcwf_items" />
          <set id="progress" appearance="title" label="" />
          <set id="response" appearance="title" label-id="workflow.set.response" />
          <!-- Отображение полей -->
          <field id="bpm:taskId" set="info" label-id="tcwf_task_id">
            <control template="/org/alfresco/components/form/controls/info.ftl" />
          </field>
          <field id="tcwf:publishOnline" label-id="tcwf_publish_online" set="items"/>
          <field id="tcwf:comments"  label-id="tcwf_comments" set="items"/>
          <field id="bpm:status" set="progress" label-id="tcwf_status"/>
          <field id="packageItems" set="items" label-id="tcwf_items_list"/>
          <field id="transitions" set="response" />
        </appearance>
      </form>
    </forms>
  </config>
  <!-- Публикация статьи -->
  <config evaluator="task-type" condition="tcwf:publishTask">
    <forms>
      <form>
        <!-- Список видимых полей -->
        <field-visibility>
          <show id="bpm:taskId" />
          <show id="bpm:status" />
          <show id="packageItems" />
          <show id="transitions" />
          <show id="tcwf:publishOnline"/>
        </field-visibility>
        <appearance>
          <!-- Блоки -->
          <set id="" appearance="title" label-id="workflow.set.task.info" />
          <set id="info" appearance="" template="/org/alfresco/components/form/3-column-set.ftl" />
          <set id="items" appearance="title" label-id="tcwf_items" />
          <set id="progress" appearance="title" label="" />
          <set id="response" appearance="title" label-id="workflow.set.response" />
          <!-- Отображение полей -->
          <field id="bpm:taskId" set="info" label-id="tcwf_task_id">
            <control template="/org/alfresco/components/form/controls/info.ftl" />
          </field>
          <field id="tcwf:publishOnline" label-id="tcwf_publish_online" set="items" read-only="true"/>
          <field id="bpm:status" set="progress" label-id="tcwf_status"/>
          <field id="packageItems" set="items" label-id="tcwf_items_list"/>
          <field id="transitions" set="response" />
        </appearance>
      </form>
    </forms>
  </config>
  <!-- Доработка статьи -->
  <config evaluator="task-type" condition="tcwf:reviseTask">
    <forms>
      <form>
        <!-- Список видимых полей -->
        <field-visibility>
          <show id="bpm:taskId" />
          <show id="bpm:status" />
          <show id="packageItems" />
          <show id="transitions" />
          <show id="tcwf:publishOnline"/>
          <show id="tcwf:comments" />
        </field-visibility>
        <appearance>
          <!-- Блоки -->
          <set id="" appearance="title" label-id="workflow.set.task.info" />
          <set id="info" appearance="" template="/org/alfresco/components/form/3-column-set.ftl" />
          <set id="items" appearance="title" label-id="tcwf_items" />
          <set id="progress" appearance="title" label="" />
          <set id="response" appearance="title" label-id="workflow.set.response" />
          <!-- Отображение полей -->
          <field id="bpm:taskId" set="info" label-id="tcwf_task_id">
            <control template="/org/alfresco/components/form/controls/info.ftl" />
          </field>
          <field id="tcwf:publishOnline" label-id="tcwf_publish_online" set="items"/>
          <field id="bpm:status" set="progress" label-id="tcwf_status"/>
          <field id="packageItems" set="items" label-id="tcwf_items_list"/>
          <field id="tcwf:comments"  label-id="tcwf_comments" set="items"/>
          <field id="transitions" set="response" />
        </appearance>
      </form>
    </forms>
  </config>
</alfresco-config>

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