Настройка рабочих процессов
Каждая сущность в Apostol CRM имеет встроенный автомат состояний. Стандартный жизненный цикл из 4 состояний покрывает большинство случаев, но его можно расширить собственными состояниями, методами, переходами и событиями.
Стандартный рабочий процесс
Вызов AddDefaultMethods(pClass) в файле init.sql сущности создаёт следующий жизненный цикл:
┌────── restore ───────┐
v |
[Created] --enable--> [Enabled] --disable--> [Disabled]
| | |
|--disable--> [Disabled] |--enable--> [Enabled]
| | |
|--delete--> [Deleted] <--delete-- [Deleted] <--delete--
Четыре типа состояний (фиксированные, определены платформой):
created-- начальное состояние сразу после создания объектаenabled-- активное/рабочее состояниеdisabled-- приостановленное/неактивное состояниеdeleted-- состояние мягкого удаления
Десять глобальных методов (доступны в любом состоянии):
create, open, edit, save, update, enable, disable, delete, restore, drop
Ключевые концепции
Типы состояний и состояния
Тип состояния -- это одна из 4 фиксированных категорий. Состояние -- это конкретное именованное состояние, принадлежащее одному из типов состояний. В пределах одного типа состояния может быть несколько состояний.
Например, у сущности «зарядная станция» есть три состояния в рамках типа состояния enabled:
enabled (тип состояния)
├── available (состояние)
├── unavailable (состояние)
└── faulted (состояние)
Методы
Метод связывает действие с классом и (опционально) с состоянием:
class-- к какому классу относится методstate-- в каком состоянии метод доступен (NULL = глобальный)action-- какое действие выполняет методvisible-- отображается ли метод в интерфейсе (по умолчанию: true)
Переходы
Переход определяет: «находясь в состоянии X, при выполнении метода Y перейти в состояние Z».
Написание собственных методов
Вместо AddDefaultMethods(pClass) можно написать собственную регистрацию методов. Ниже -- реальный пример из сущности «зарядная станция»:
CREATE OR REPLACE FUNCTION AddStationMethods (
pClass uuid
)
RETURNS void
AS $$
DECLARE
uState uuid;
rec_type record;
rec_state record;
rec_method record;
BEGIN
-- Шаг 1: Создаём глобальные методы (не зависящие от состояния)
PERFORM DefaultMethods(pClass);
-- Шаг 2: Создаём состояния и методы, специфичные для состояний
FOR rec_type IN SELECT * FROM StateType
LOOP
CASE rec_type.code
WHEN 'created' THEN
uState := AddState(pClass, rec_type.id, rec_type.code, 'Created');
PERFORM AddMethod(null, pClass, uState, GetAction('enable'), null, 'Enable');
PERFORM AddMethod(null, pClass, uState, GetAction('delete'), null, 'Delete');
WHEN 'enabled' THEN
-- Несколько состояний в пределах типа состояния 'enabled':
uState := AddState(pClass, rec_type.id, 'available', 'Available');
PERFORM AddMethod(null, pClass, uState, GetAction('unavailable'),
null, 'Unavailable', null, false);
PERFORM AddMethod(null, pClass, uState, GetAction('faulted'),
null, 'Faulted', null, false);
PERFORM AddMethod(null, pClass, uState, GetAction('disable'),
null, 'Disable');
uState := AddState(pClass, rec_type.id, 'unavailable', 'Unavailable');
PERFORM AddMethod(null, pClass, uState, GetAction('available'),
null, 'Available', null, false);
PERFORM AddMethod(null, pClass, uState, GetAction('faulted'),
null, 'Faulted', null, false);
PERFORM AddMethod(null, pClass, uState, GetAction('disable'),
null, 'Disable');
uState := AddState(pClass, rec_type.id, 'faulted', 'Faulted');
PERFORM AddMethod(null, pClass, uState, GetAction('available'),
null, 'Available', null, false);
PERFORM AddMethod(null, pClass, uState, GetAction('disable'),
null, 'Disable');
WHEN 'disabled' THEN
uState := AddState(pClass, rec_type.id, rec_type.code, 'Disabled');
PERFORM AddMethod(null, pClass, uState, GetAction('enable'), null, 'Enable');
PERFORM AddMethod(null, pClass, uState, GetAction('delete'), null, 'Delete');
WHEN 'deleted' THEN
uState := AddState(pClass, rec_type.id, rec_type.code, 'Deleted');
PERFORM AddMethod(null, pClass, uState, GetAction('restore'), null, 'Restore');
PERFORM AddMethod(null, pClass, uState, GetAction('drop'), null, 'Drop');
END CASE;
END LOOP;
-- Шаг 3: Создаём стандартные переходы для глобальных методов
PERFORM DefaultTransition(pClass);
-- Шаг 4: Создаём собственные переходы
FOR rec_state IN SELECT * FROM State WHERE class = pClass
LOOP
CASE rec_state.code
WHEN 'created' THEN
FOR rec_method IN SELECT * FROM Method WHERE state = rec_state.id
LOOP
IF rec_method.actioncode = 'enable' THEN
PERFORM AddTransition(rec_state.id, rec_method.id,
GetState(pClass, 'unavailable'));
END IF;
IF rec_method.actioncode = 'delete' THEN
PERFORM AddTransition(rec_state.id, rec_method.id,
GetState(pClass, 'deleted'));
END IF;
END LOOP;
WHEN 'available' THEN
FOR rec_method IN SELECT * FROM Method WHERE state = rec_state.id
LOOP
IF rec_method.actioncode = 'unavailable' THEN
PERFORM AddTransition(rec_state.id, rec_method.id,
GetState(pClass, 'unavailable'));
END IF;
IF rec_method.actioncode = 'faulted' THEN
PERFORM AddTransition(rec_state.id, rec_method.id,
GetState(pClass, 'faulted'));
END IF;
IF rec_method.actioncode = 'disable' THEN
PERFORM AddTransition(rec_state.id, rec_method.id,
GetState(pClass, 'disabled'));
END IF;
END LOOP;
-- ... аналогично для 'unavailable', 'faulted', 'disabled', 'deleted'
END CASE;
END LOOP;
END;
$$ LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = kernel, pg_temp;
В результате создаётся следующий автомат состояний:
┌─── heartbeat (self) ───┐
v |
[Created] --enable--> [Unavailable] --available--> [Available]
| | |
|--delete--> |--faulted--> [Faulted] |
| |--disable--> | |--faulted-->
| | |--disable-->
v v v
[Deleted] <--delete-- [Disabled] <--disable--
Основные API-функции
AddState
uState := AddState(pClass, pStateType, pCode, pLabel);
pClass-- UUID классаpStateType-- UUID типа состояния (изGetStateType('enabled')или переменной цикла)pCode-- уникальный код состояния в пределах класса (например,'available')pLabel-- отображаемая подпись
AddMethod
uMethod := AddMethod(pId, pClass, pState, pAction, pCode, pLabel, pSequence, pVisible);
pState-- UUID состояния (NULL = глобальный метод, доступный в любом состоянии)pAction-- UUID действия изGetAction('...')pVisible-- показывать ли метод в интерфейсе (по умолчанию: true, для автоматических методов установитеfalse)
AddTransition
PERFORM AddTransition(pCurrentState, pMethod, pNewState);
Определяет: находясь в состоянии pCurrentState, при выполнении метода pMethod перейти в состояние pNewState.
Видимость методов
Методы могут быть видимыми или скрытыми:
-- Скрытый: вызывается системой, а не пользователем
PERFORM AddMethod(null, pClass, uState, GetAction('heartbeat'),
null, 'Heartbeat', null, false);
-- Видимый: пользователь может вызвать его из интерфейса
PERFORM AddMethod(null, pClass, uState, GetAction('disable'),
null, 'Disable');
Используйте скрытые методы для автоматической смены состояний (heartbeat, обновления статуса от внешних систем, фоновые процессы).
Действия без смены состояния
Некоторые действия выполняют свои обработчики событий, не меняя состояние. Вызов AddTransition для них не нужен:
WHEN 'enabled' THEN
uState := AddState(pClass, rec_type.id, rec_type.code, 'Enabled');
-- Эти действия остаются в том же состоянии:
PERFORM AddMethod(null, pClass, uState, GetAction('submit'), pVisible => false);
PERFORM AddMethod(null, pClass, uState, GetAction('confirm'), pVisible => false);
PERFORM AddMethod(null, pClass, uState, GetAction('reconfirm'));
Собственные обработчики событий
При добавлении собственных действий также нужны события и их обработчики. В функции Add<Entity>Events:
IF r.code = 'available' THEN
PERFORM AddEvent(pClass, uEvent, r.id, 'Station available',
'EventStationAvailable();');
PERFORM AddEvent(pClass, uEvent, r.id, 'Change state',
'ChangeObjectState();');
END IF;
Вызов ChangeObjectState() важен для собственных действий, не входящих в стандартный жизненный цикл -- именно он запускает фактический переход между состояниями. Стандартные действия (enable, disable, delete, restore) обрабатывают смену состояния автоматически.
Паттерны проектирования
| Паттерн | Когда использовать | Пример |
|---|---|---|
AddDefaultMethods(pClass) | Простой CRUD со стандартным жизненным циклом | Регион, валюта |
| Свои методы, 1 состояние на тип | Стандартный цикл с дополнительными действиями | Клиент (submit/confirm) |
| Свои методы, N состояний на тип | Сложный цикл с подсостояниями | Станция (available/unavailable/faulted) |
| Скрытые методы | Автоматические/системные действия | Heartbeat, синхронизация статуса |
| Действия без перехода | Действия, выполняющие только события | Submit, confirm |