Bproc. Переменная типа "Entity list" и параметр формы

Добрый день.

Собственно, параметр формы:
image

Так выглядит выбор параметра:
image
Значения могут быть выбраны, но не могут быть удалены. Кнопка ОК - неактивна.
Если изменить тип UI Component на PickerField поле для выбора неактивно.

Все. Кажется, я понял. Это происходит на этапе claim. Можно запросить доработку: на этапе claim не добавлять к форме editable-поля.

PS. Есть ли планы по добавлению локализации к заголовкам диалога и кнопкам формы?

Добрый день!

Завели тикет.

Что касается локализации, в планах есть, но не в ближайших.

А можно уточнить горизонт планов? Поскольку такой вопрос уже задавался около полугода назад…

Похоже, что я могу сделать это самостоятельно: нужно отнаследоваться от абстрактного DynamicProcessForm где посмотреть в какой-нибудь “определенный” пакет сообщений, переустановить Caption и расширить DynamicTaskProcessForm.

Может в качестве компромисса рассмотрите вариант указать в Caption полный путь до сообщения (в качестве примера msg://com.company.system.entity.request/Request.functionalRequirements)?
Дабы мне не городить огород?

Путь до сообщения в сaption указать можно, но такое решение будет половинчатым. Ведь кроме подписей у полей и кнопок в том же инпут-диалоге есть ещё и имя задачи в заголовке окна. Также имя задачи и имя процесса отображаются в дереве в экране MyTasks. У вас были какие-то идеи как их локализовать?

Screenshot 2020-06-19 at 10.52.12

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

Я это прекрасно понимаю. На безрыбье и рак - щука.

Это вы зря, так сказать… Их есть у меня. Я пару дней назад написал большой пост на эту тему, но он остался в черновиках - не смог я довести мысль до конца.
Но некоторые мысли озвучу, если уж вы просите.
Disclaimer. Мне очень понравился аддон. Очень широкие возможности. Но в большинстве своем они рассчитаны на тесную интеграцию куба-экранов и процессов.

Мне бы хотелось пойти по пути, по которому следуют многие документооборотные системы, т.е. максимально абстрагировать сущность от процесса, чтобы вся логика описывалась в модели процесса.

  1. Т.е. развивать нужно, по моему мнению, часть с InputDialog-формами. Необходимо расширить возможности по настройке этих форм (банально: ширину колонки при открытии не в диалоговом режиме, дескрипшены и хелп к полям).
  2. Все-таки локализация. На текущем проекте, где и планируется bproc нужна именно она.

(!) Далее как вижу я. Вы можете другие варианты реализовать. Думаю, цель будет ясна*


  1. Возможность запуска процесса из формы-редактирования (а может и browse )
    3.1 Справочник соответствия process definition-entity (или entity-editor-screen)
    3.2 Добавить аннотацию к экрану, что с него может быть запущен рабочий процесс (вариант мне не очень нравится, так как опять нужно лезть в разработку)
    3.3 При инициализации экрана анализировать есть ли аннотация (п.3.2)
    3.4 Добавлять на форму кнопку старта процесса, делать запрос в справочник (п.3.1) запускать процесс или показывать модальное окно со списком доступных моделей процессов

Обратил внимание, что Task-и - это неперсистивные сущности. Как бы сформировать ссылку на таску, которую можно положить в письмо. Может можно вызвать сервис/бин, который откроет форму (говорю о Input Dialog) таски? Если нет, то продумать бы эту возможность.

Извиняюсь за некий сумбур. За следующую недели, думаю, наберу еще.

Вот еще немаловажно…
Представим, что у меня классическое согласование документа. По окончанию этого согласования мне хочется получить некий документ - назовем его “лист согласования” (дата начала, окончания, результат, какой-то комментарий).
Сейчас как я понимаю, у нас нет возможности посмотреть даже выбранные пользователями outcome, они хранятся только внутри xml (поправьте меня если я не прав).
Какой бы вы предложили вариант решения данной проблемы? Повесить слушатель на таски и писать эти результаты и комментарии в “свою” сущность? Или можно получить таски с исполнителями, датами, и результатами с помощью одного из сервисов например, bprocHistoricService? Что-то с наскока не получилось. Может подскажите как?
В общем было бы здорово получить плюсом к BpmnDiagramViewerFragment (это вообще киллер-фича) еще и возможность сгенерировать некий отчет по процессу.

Как бы сформировать ссылку на таску, которую можно положить в письмо

Можно сделать свой LinkHandlerProcessor. Вроде такого:


package com.company.sample.web;

import com.haulmont.addon.bproc.entity.TaskData;
import com.haulmont.addon.bproc.service.BprocTaskService;
import com.haulmont.addon.bproc.web.processform.ProcessFormScreens;
import com.haulmont.cuba.gui.screen.Screen;
import com.haulmont.cuba.web.AppUI;
import com.haulmont.cuba.web.sys.linkhandling.ExternalLinkContext;
import com.haulmont.cuba.web.sys.linkhandling.LinkHandlerProcessor;
import org.springframework.stereotype.Component;

import javax.inject.Inject;


@Component
public class InputDialogProcessFormLinkHandler implements LinkHandlerProcessor {

    @Inject
    private BprocTaskService bprocTaskService;

    @Inject
    private ProcessFormScreens processFormScreens;

    @Override
    public boolean canHandle(ExternalLinkContext linkContext) {
        return "openProcessForm".equals(linkContext.getAction());
    }

    @Override
    public void handle(ExternalLinkContext linkContext) {
        String taskId = linkContext.getRequestParams().get("taskId");
        TaskData taskData = bprocTaskService.createTaskDataQuery()
                .taskId(taskId)
                .singleResult();
        Screen taskProcessForm = processFormScreens.createTaskProcessForm(taskData,
                AppUI.getCurrent().getTopLevelWindowNN().getFrameOwner());
        taskProcessForm.show();
    }
}

Процессные формы тогда будут открываться по URL вида http://localhost:8080/app/openProcessForm?taskId=01a66194-bd78-b7c7-aba1-5048fcba5b74z

Экшн openProcessForm должен быть объявлен в свойстве приложения cuba.web.linkHandlerActions

1 симпатия

В xml с процессом хранятся описания ауткомов. Сами же решения записываются в переменные процесса. Ауткамы кладутся в особую процессную переменную, её значение имеет тип com.haulmont.addon.bproc.data.OutcomesContainer. Контейнер нужен на случай multi-instance юзер-тасок. Каждый контейнер содержит в себе коллекцию объектов com.haulmont.addon.bproc.data.Outcome. В каждом таком объекте хранится информация о собственно решении, дате этого решения и пользователе.

Сейчас получить список объектов Outcome через bprocHistoricService можно, например, так:

package com.company.sample.service;

import com.haulmont.addon.bproc.data.Outcome;
import com.haulmont.addon.bproc.data.OutcomesContainer;
import com.haulmont.addon.bproc.engine.variable.OutcomesContainerVariableType;
import com.haulmont.addon.bproc.entity.HistoricVariableInstanceData;
import com.haulmont.addon.bproc.entity.ProcessInstanceData;
import com.haulmont.addon.bproc.service.BprocHistoricService;
import org.springframework.stereotype.Service;

import javax.inject.Inject;
import java.util.List;
import java.util.stream.Collectors;

@Service(TaskOutcomeHistoryService.NAME)
public class TaskOutcomeHistoryServiceBean implements TaskOutcomeHistoryService {

    @Inject
    private BprocHistoricService bprocHistoricService;

    @Override
    public List<Outcome> getOutcomesHistory(ProcessInstanceData processInstanceData) {
        List<HistoricVariableInstanceData> variableInstanceDataList = bprocHistoricService.createHistoricVariableInstanceDataQuery()
                .processInstanceId(processInstanceData.getId())
                .orderByVariableName().asc()
                .list();

        List<Outcome> outcomes = variableInstanceDataList.stream()
                .filter(variableInstanceData ->
                        OutcomesContainerVariableType.TYPE_NAME.equals(variableInstanceData.getVariableTypeName()) ||
                                variableInstanceData.getValue() instanceof OutcomesContainer)
                .map(historicVariableInstanceData -> (OutcomesContainer) historicVariableInstanceData.getValue())
                .flatMap(outcomesContainer -> outcomesContainer.getOutcomes().stream())
                .collect(Collectors.toList());
        return outcomes;
    }
}

Подумаем, как сделать этот процесс более удобным.

2 симпатии

Спасибо. Все работает. Но есть нюанс.
Правильно ли я понимаю, что если мы повторно зашли в блок, то “новый” outcome заменит “старый”?

Плюсом еще подскажите, пожалуйста,

final List<ProcessInstanceData> list = bprocRuntimeService.createProcessInstanceDataQuery()
      .processDefinitionKey(PROCESS_DEFINITION_KEY)
      .orderByStartTime().desc()
      .list();

Не возвращает завершенных инстансов. Так задумывалось? Как бы получить в том числе завершенные?

Судя по всему заходить на решение этого вопроса нужно через HistoricActivityInstanceData.
UPD: получается, что не все так просто. В userTask есть все, но нет выбранного outcome.
UPD2: добавлю, что получить переменную по taskId или executionId у меня не получилось:

List<HistoricActivityInstanceData> historicActivityInstances = bprocHistoricService.createHistoricActivityInstanceDataQuery()
                .processInstanceId(processInstanceData.getId())
                .orderByHistoricActivityInstanceStartTime()
                .desc()
                .list();

final List<HistoricVariableInstanceData> variables = historicActivityInstances.stream()
                .filter(historicActivityInstanceData -> historicActivityInstanceData.getActivityType().equals("userTask"))
                .flatMap(historicActivityInstanceData -> bprocHistoricService.createHistoricVariableInstanceDataQuery()
                        .executionId(historicActivityInstanceData.getExecutionId())
                        .list()
                        .stream()
                )
                .collect(Collectors.toList());

Лист variables пустой.

Такой API у Flowable. *Runtime сервисы возвращают только активные инстансы. *Historic сервисы возвращают и активные и завершённые инстансы. Для поиска завершённых экземпляров процесса можно воспользоваться bprocHistoricService.createHistoricProcessInstanceDataQuery(). Пример использования можно посмотреть в com.haulmont.addon.bproc.web.screens.processinstance.ProcessInstanceBrowse

Если нужно отследить историю изменений ауткамов с учётом повторых заходов в одну и ту же задачу, то можно воспользоваться следующим сервисом из API Flowable:

ProcessEngines.getDefaultProcessEngine().getHistoryService()
                .createHistoricDetailQuery()
                .variableUpdates()
                .processInstanceId(processInstanceData.getId())
                .list();

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

bproc.flowable.historyLevel=full

Альтернативный способ записи журнала согласования - это ловить события завершения задач и записывать значения ауткамов в какое-нибудь собственное хранилище. Это можно сделать, например, через FlowabelEventListener вроде такого:

package com.company.sample.core.listener;

import com.haulmont.addon.bproc.data.Outcome;
import com.haulmont.addon.bproc.data.OutcomesContainer;
import org.flowable.common.engine.api.delegate.event.FlowableEntityEvent;
import org.flowable.common.engine.api.delegate.event.FlowableEvent;
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Map;

public class MyTaskListener implements FlowableEventListener {

    private static final Logger log = LoggerFactory.getLogger(MyTaskListener.class);

    @Override
    public void onEvent(FlowableEvent event) {
        FlowableEntityEvent entityEvent = (FlowableEntityEvent) event;
        TaskEntity taskEntity = (TaskEntity) entityEvent.getEntity();
        Map<String, Object> variables = taskEntity.getVariables();
        OutcomesContainer outcomesContainer = (OutcomesContainer) variables.values().stream()
                .filter(variable -> variable instanceof OutcomesContainer)
                .findFirst()
                .orElse(null);
        if (outcomesContainer != null) {
            List<Outcome> outcomes = outcomesContainer.getOutcomes();
            log.info("Task {} completed with outcome {}", taskEntity.getTaskDefinitionKey(),
                    outcomes.get(0).getOutcomeId());
        }
    }

    @Override
    public boolean isFailOnException() {
        return false;
    }

    @Override
    public boolean isFireOnTransactionLifecycleEvent() {
        return false;
    }

    @Override
    public String getOnTransaction() {
        return null;
    }
}

Зарегистрировать листенер нужно в модели на панели свойств процесса:

image

1 симпатия

Мксим, спасибо. Вроде бы получилось как нужно.

Есть еще вопрос. Теперь у меня в руках есть исторические данные по ауткамам и исторические данные по переменным, измененных в задачах, где эти ауткамы были выбраны. Есть ли возможность связать их между собой?
Поясню. Вот добавляю я на форму поле комментарий и соответственно переменную для хранения. На первом кругу я выбираю ауткам “отклонить” и заполняю комментарий, на втором - “согласовать” и заполняю комментарий. Как мне соединить ауткамы и комментарии?

У класса HistoricDetail есть метод getActivityInstanceId() - на вид вроде бы то, что нужно, попробуйте поэкспериментировать с ним - сгруппировать переменные по этом полю.

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

    @Override
    public void onEvent(FlowableEvent event) {
        FlowableEntityWithVariablesEvent entityEvent = (FlowableEntityWithVariablesEvent) event;
        TaskEntity taskEntity = (TaskEntity) entityEvent.getEntity();
        Map<String, Object> variables = entityEvent.getVariables();
        //...
    }

FlowableEntityWithVariablesEvent.getVariables() согласно javadoc return the variables created together with the entity.

1 симпатия

Максим, добрый день.
Тема ушла слишком далеко от изначальной, но чтобы не ломать контекст, задам вопрос тут же

Погружаюсь все глубже во flowable и возникает вопрос:
Если модифицировать предложенный вами запрос и использовать не processInstanceId, а taskId, то запрос ничего не возвращает, что кажется мне странным, поскольку изменения outcome-ов должны происходить в контексте задачи (я предполагаю). Или это не так?

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

  1. Хочу вывести информацию также и о задачах, которые еще не выполнены
  2. Необходимо вывести также и дату старта задачи
  3. Аналогично со сроком

Т.е. необходимо отталкиваться от информации о задачах HistoricTaskInstance. Проблема в присоединении к ним результатов выполнения (ауткамах).

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

поскольку изменения outcome-ов должны происходить в контексте задачи

Outcome является переменной процесса, а не локальной переменной таски, т.к. к информации об ауткамах нужен доступ уже после того, как задача завершена (на гейтвеях, например)

По поводу извлечения исторической информации о выполнении задачи - сходу как-то решение не рождается. Зафиксировали задачку на подумать.

1 симпатия

Просто для информации…
Я пошел по пути создания слушателей, как вы предлагали выше.
Засунул все в аккардеон. И вроде бы результат не плох, хотя и не очень презентабелен:
image
Но лучше, чем ничего.

Из проблем: объявлять для каждой из схем слушатель пяти типов, несколько напрягает. А как я понял, глобально на уровне платформы их не объявить… Пока это единственная проблема, за исключение того, что не понятно в каком состоянии находится процесс на данный момент (озвучил проблемы в другом топике).