Производственная деятельность предприятия связана с работой ответственных должностных лиц над одним или несколькими документами. Порядок прохождения документов определён нормативными актами. Каждое должностное лицо заполняет ту или иную часть документа, согласовывает, утверждает документ, возвращает его на доработку, участвует в выполнении работ по документу.
Бизнес процессы, описывающие производственную деятельность принято представлять в формате BPMN, а системы, автоматизирующие бизнес-процессы, часто создают с использованием ИТ-платформ типа Camunda. Camunda выступает в роли BPMN-движка.
Camunda довольно специфический продукт, требующий привлечения специалистов соответствующей квалификации. Можно предложить подход к автоматизации бизнес-процессов, не требующий специальной подготовки аналитиков и разработчиков.
Подход к автоматизации бизнес-процессов
Если предметная область имеет детерминированный характер, то можно применять парадигму автоматного программирование, в рамках которой автоматизируемая производственная деятельность рассматривается с точки зрения теории конечных автоматов (Finite state machines).
Прохождение документа по своему жизненному циклу можно представить в виде конечного автомата: этапы прохождения документа определяют состояния автомата, а действия должностных лиц над документом определяют переходы между состояниями. Конечный автомат характеризуются набором состояний и правилами переходов между этими состояниями.
Детерминированный конечный автомат в результате входного воздействия может перейти из одного состояния в строго одно следующее состояний. Это состояние зависит от входного воздействия.
На практике документ после рассмотрения должностным лицом может быть дальше передан на рассмотрение более чем одному должностному лицу. Соответствующий автомат будет иметь несколько активных состояний и будет являться недетерминированным конечным автоматом.
Добавим в недетерминированный конечный автомат особый тип состояния «Точка сборки»: переход автомата из точки сборки в следующие состояния происходит не по команде, а автоматически, когда осуществлён переход в точку сборки изо всех состояний, имеющих выход в эту точку сборки. Здесь просматривается аналогия логики работы точки сборки с логикой работы перехода в сетях Петри.
Назовём такой автомат структурным автоматом.
Веб-приложение для реализация бизнес-процесса
Автоматизация бизнес-процесса, связанного с работой должностных лиц над документами осуществляется в веб-приложении. Документу соответствует веб-страница. Веб-страница документа содержит набор элементов управления, включая дата-гриды, диаграммы, командные кнопки или ссылки и т.д. Каждое состояние документа может иметь свой набор элементов управления на веб-странице.
Логикой прохождения документа по своему жизненному циклу управляет движок веб-приложения, реализующий структурный автомат. Логика приложения зашита в таблицу переходов автомата и отделена от кодового каркаса движка веб-приложения. Разработка логики автоматизируемых бизнес-процессов, создание диаграмм переходов автоматов является конфигурированием системы, осуществляется аналитиками и не требует программирования.
Использование конечных (не структурных) автоматов для автоматизации бизнес-процессов представлено в свидетельствах о государственной регистрации программы для ЭВМ №№ 2016661880, 2017661726, 2019619743. Я не автор этих разработок. Там же предлагается каждое состоянием документа связать с определённой ролью или ролями пользователя. Будем следовать этому подходу.
Потребуем:
если из состояния есть переход в несколько состояний, то этим состояниям соответствуют разные роли;
к точкам сборки и финальному состоянию роли не привязываются.
Если документ находится в определённом состоянии, то внести изменения в документ и перевести его в другие состояния может только роль, связанная с этим состоянием. Для перевода документа в другие состояния служат командные кнопки или ссылки на веб-странице.
Для каждого состояния документа для роли, связанной с этим состоянием, имеется определённый набор действий, который роль должна выполнить над элементами управления веб-страницы документа. Только после выполнения всего набора действий на веб-странице активируются командные кнопки или ссылки для перевода документа в другие состояния.
Если роль открыла документ с неактивным состоянием, привязанным к этой роли, то роль имеет к элементам управления документа доступ только на чтение или вообще не может открыть документ.
Если роль открыла документ с состоянием, не привязанным к этой роли, то роль имеет к элементам управления документа доступ только на чтение или вообще их не может открыть документ.
Документ может иметь несколько экземпляров. Каждый экземпляр документа находится в своих определённых активных состояниях.
Юридическая значимость электронного документа
Переход электронного документа в новое состояние подтверждается простой электронной подписью. В качестве простой электронной подписи рассматривается имя и пароль учётной записи пользователя, используемые для входа в веб-приложение. Между пользователями и администрацией составляется соглашение, устанавливающее признание электронного документа, подписанного простой электронной подписью, равнозначным документу на бумажном носителе, подписанным собственноручной подписью.
Реализация движка структурного автомата
Рассматривается реализация движка структурного автомата для одного экземпляра документа. Веб-версия движка реализована на Python и PHP. Логика реализована с помощью SQL-запросов.
Исходные коды, примеры структурных автоматов в виде файлов SQLite-баз и руководство по установке движка можно посмотреть на github.
Скрипт - движок структурного автомата. Версия на PHЗ. Эмуляция входа роли в веб-приложение. Показано, что вошедшая в приложение роль может открыть страницу только активного и привязанного к этой роли ней состояния.
<html><table><tr><td><img width="85%" src=fsmxSPP.png></td><td valign="top">
<p><a href=newActiveStatesS.php>Reset to initial state!</a>
<?php
$self=$_SERVER['PHP_SELF'];
$addr=$_SERVER['SERVER_ADDR'];
$db = new SQLite3('fsmx.db');
session_start();
if (!empty($_SESSION['role'])) echo '<p><a href='.$self.'?Logout=1>Logout.</a></p>';
if(isset($_GET['role'])) {
$role=$_GET['role'];
$_SESSION['role']=$role;
header('Location: http://'.$addr.$self);
}
if(isset($_GET['Logout'])) {
session_destroy();
header('Location: http://'.$addr.$self);
}
//Показываем активные состояния и роли
$result = $db->query('select activeState, role from activeStates, roleStates where activeStates.activeState=roleStates.state');
echo '<h2><p>Active states:</h2>';
while ($row = $result->fetchArray(SQLITE3_ASSOC)){
$r=$row['role']; $as=$row['activeState'];
echo '<p>State='.$as.' for role='.$r;}
if (!empty($_SESSION['role'])) echo '<h2><p>Logged in as role = '.$_SESSION['role'].'.</h2>';
if (empty($_SESSION['role']))
echo '<p><br><br>Login as role from "Active states" list. <p>Enter an integer - role number. <form action='.$self.' method=get> <input type=text" name=role><input type=submit Value=LogIn></form>';
else {
$role=$_SESSION['role'];
$as = $db->querySingle('select activeState from activeStates a, roleStates r where a.activeState= r.state and r.role='.$role);
if (empty($as)){
echo '<p><h3>Role '.$role.' has no active states and cannot open any web page.';
echo '<p>Logout and login with a role that has active states: see list "Active states".</h3>';
}
else {
echo '<p><h2>-----------------------------------------------------------------------------</h2></p>';
echo '<p><h2>This is the web page for state='.$as.' and role='.$role.'.</h2></p>';
echo '<p><h3>There will be many web elements for input and editing: data grids, charts, etc.</h3>';
echo '<p><h3>Commands for transition to other states:</h3>';
//Из состояния команда идёт в одно следующее состояние
$sql='select command, count(command) c from fsmx, roleStates where fsmx.state=roleStates.state and fsmx.state='.$as.' and role='.$role.' group by command having c=1';
$result = $db->query($sql);
//Перебираем команды исходящие из состояния
while ($row = $result->fetchArray(SQLITE3_ASSOC)){
$cmd=$row['command'];
//Формируем ссылку для перехода в другое состояние
$nextState = $db->querySingle('select nextState from fsmx LEFT JOIN roleStates ON fsmx.state=roleStates.state where fsmx.state='.$as.' and role='.$role.' and command='.$cmd);
echo '<p><a href='.$self.'?command='.$cmd.'&as='.$as.'>Command = '.$cmd.'. </a>';
$nextRole = $db->querySingle('select role from roleStates where state='.$nextState);
//Показываем, какое будет после выполнения команды следующее состояние и связанная с ним роль
echo ' Next state = '.$nextState;
if (!empty($nextRole)) echo ' for role = '.$nextRole.'.';
}
//Из состояния команда идёт в несколько следующих состояний
$result = $db->query('select command, count(command) c from fsmx, roleStates where fsmx.state=roleStates.state and fsmx.state='.$as.' and role='.$role.' group by command having c>1 order by command');
//Перебираем команды исходящие из состояния
while ($row = $result->fetchArray(SQLITE3_ASSOC)){
$cmd=$row['command'];
//Формируем ссылку для прехода в другИЕ состояниЯ
echo '<p><a href='.$self.'?commandX='.$cmd.'&as='.$as.'>Command = '.$cmd.'. </a>';
//Показываем, какИЕ будУТ после выполнения команды следующИЕ состояниЯ и связаннЫЕ с нимИ ролИ
$result1 = $db->query('select nextState, role from fsmx LEFT JOIN roleStates ON fsmx.state=roleStates.state where fsmx.state='.$as.' and role='.$role.' and command='.$cmd);
while ($row1 = $result1->fetchArray(SQLITE3_ASSOC)){
$nextState = $row1['nextState'];
$nextRole = $db->querySingle('select role from roleStates where state='.$nextState);
echo ' Next state = '.$nextState;
if (!empty($nextRole)) echo ' for role = '.$nextRole.'.';}
}
}
}
//Отрабатываем команду перехода в следующее состояние
if(isset($_GET['command'])) {
$as=$_GET['as'];
//Удаляем состояние из активных состояний
$db->query('delete from activeStates where activeState='.$as);
//Добавляем следующее состояние в активные состояния
$nextState = $db->querySingle('select nextState from fsmx where state='.$as.' and command ='.$_GET['command']);
$db->query('insert into activeStates(activeState) values ('.$nextState.')');
//Проверяем, что следующее состояние - это точка сборки у которой все входы активны
$gate = $db->querySingle('select gate from gatecnt, currentgatecnt where gatecnt.cnt=currentgatecnt.cnt and gatecnt.gate='.$nextState);
if(isset($gate)) {
//Удаляем точку сборки из активных состояний
$db->query('delete from activeStates where activeState='.$gate);
//Активируем все состояния, выходящие из точки сборки
$result = $db->query('select nextState from fsmx where state='.$gate);
while ($row = $result->fetchArray(SQLITE3_ASSOC))
$db->query('insert into activeStates(activeState) values ('.$row['nextState'].')');}
header('Location: http://'.$addr.'/'.$self); }
//header('Location: '.'/'.$self); }
//Отрабатываем команду перехода в следующИЕ состояниЯ
if(isset($_GET['commandX'])) {
$cmd=$_GET['commandX'];
$as=$_GET['as'];
//Удаляем состояние из активных состояний
$db->query('delete from activeStates where activeState='.$as);
//Перебираем следующие состояния
$result = $db->query('select nextState from fsmx where state='.$as.' and command ='.$cmd);
while ($row = $result->fetchArray(SQLITE3_ASSOC)){
$nextState=$row['nextState'];
$db->query('insert into activeStates(activeState) values ('.$nextState.')');
//Проверяем, что следующее состояние - это точка сборки у которой все входы активны
$gate = $db->querySingle('select gate from gatecnt, currentgatecnt where gatecnt.cnt=currentgatecnt.cnt and gatecnt.gate='.$nextState);
if(isset($gate)) {
//Удаляем точку сборки из активных состояний
$db->query('delete from activeStates where activeState='.$gate);
//Активируем все состояния, выходящие из точку сборки
$result1 = $db->query('select nextState from fsmx where state='.$gate);
while ($row1 = $result->fetchArray(SQLITE3_ASSOC))
$db->query('insert into activeStates(activeState) values ('.$row1['nextState'].')');}}
header('Location: http://'.$addr.'/'.$self); } ?>
</td></tr></table></html>
SQLite-база fsmx.db содержит три таблицы:
fsmx — таблица переходов структурного автомата с колонками state (номер состояния), command (номер команды) и nextState (номер следующего состояния);
roleStates — таблица связки ролей с состояниями с колонками state (номер состояния) и role (номер роли);
activeState — таблица с колонкой, содержащая перечень номеров активных состояний структурного автомата.
В базу добавлены sql-представления:
1. Число входов у точки сборки. Признак точки сборки: command = -1.
CREATE VIEW gatecnt(gate, cnt) as
select nextstate gate, count(nextstate) cnt from fsmx
where nextState in (select f2.state from fsmx f2 where command = -1)
group by nextState
2. Число повторяющихся активных состояний. Используется для активации точки сборки, когда число переходов в точку сборки равно числу её входов.
CREATE VIEW currentgatecnt(state,cnt) as
select activeState, count(activeState) c from activeStates
group by activeState having c > 1
Скрипт для задание номера начального активного состояния, например 20, для примера 2 выше.
PHP:
<?php
$db = new SQLite3('fsmx.db');
$db->query('delete from activeStates');
$db->query('insert into activeStates(activeState) values (20)');
$addr=$_SERVER['SERVER_ADDR'];
header('Location: http://'.$addr.'/fsmx.php');
?>