Содержание статьи
WPS: АСИНХРОННЫЕ БИЗНЕС-ПРОЦЕССЫ ДЛЯ SUITECRM 7 И 8 ВМЕСТО ВСТРОЕННОГО AOW WORKFLOW
Мы выложили в open source под лицензией MIT Workflow Processing Service (WPS) — отдельный сервис для бизнес-процессов над записями CRM. Вместе с ним вышли два плагина: для SuiteCRM 7 и для SuiteCRM 8. В статье — какие проблемы встроенного движка процессов SuiteCRM мы решали, как устроен WPS и как подключить его к своей CRM.
-
Сервис: madarch-labs/Workflow-Processing-Service
-
Плагин для SuiteCRM 7: madarch-labs/WPS_SuiteCRM7_plugin
-
Плагин для SuiteCRM 8: madarch-labs/WPS_SuiteCRM8_plugin
Что не так со встроенными процессами SuiteCRM
В SuiteCRM есть модуль WorkFlow (AOW). Для простых правил вида «при сохранении сделки поменять поле» его хватает. Когда сценарии усложняются, начинаются ограничения. Мы разобрали код модуля, и вот что видно.
Встроенный AOW: условия и фиксированный набор действий.
-
Логика выполняется внутри сохранения. AOW подключён к logic hook after_save. Процесс с режимом «при сохранении» выполняет все действия прямо в запросе пользователя: человек нажал «Сохранить» и ждёт, пока создадутся записи и уйдут письма. Ошибка в действии становится ошибкой сохранения. Альтернатива — запуск по cron, но тогда реакция откладывается.
-
Процесс видит одну запись. Доступны поля сохранённой записи и записей, связанных с ней отношениями CRM. Найти запись по произвольному полю (по имени, email, внешнему коду) или пройти цепочку «обращение → аккаунт → менеджер аккаунта» нельзя.
-
Нет композиции. Процесс не может запустить другой процесс и передать ему данные. Цепочки получаются только неявно: процесс меняет запись, её сохранение запускает следующий workflow. Связи в аудите при этом нет, контроля циклов тоже.
-
Скудный аудит и нет повторов. В Process Audit видны запись и статус Complete или Failed. Текста ошибки, запроса и ответа там нет, политики повторов для временных сбоев тоже нет.
-
Нельзя проверить процесс без настоящих данных — ни ручного запуска с тестовым объектом, ни песочницы.
Как устроен WPS
Идея простая: CRM только сообщает о событии, а вся логика живёт в отдельном сервисе.

CRM публикует событие в Redis Stream; WPS читает его, выполняет процесс и работает с CRM через REST API плагина.
-
Плагин в CRM регистрирует logic hook after_save, и при сохранении записи хук выполняет одну команду: XADD в Redis Stream crm_events. В событии — модуль, id и все сохранённые поля записи. Redis-клиент написан на чистом PHP, расширение phpredis не нужно. Если Redis недоступен, запись всё равно сохраняется: ожидание ограничено таймаутом в 1 секунду, ошибка пишется в лог CRM.
-
WPS (PHP 8.3 + Swoole 6) читает стрим через consumer group. Сообщение подтверждается только после обработки. WPS находит активные процессы для модуля, проверяет условия и выполняет действия. С CRM он работает через небольшой REST API, который даёт тот же плагин: чтение по id, поиск по полю, создание, обновление, список модулей. Это JSON:API, ключ X-Auth-Key меняется каждый час.
-
Хранилища. PostgreSQL хранит процессы с версиями, выполнения и аудит шагов. Redis — состояние текущих выполнений, кэши и ключи идемпотентности.
-
Интерфейс на Vue 3 + Element Plus (русский и английский) и REST API для автоматизации.
Процесс: переменные, условия и цепочки записей
Процесс привязан к модулю CRM и состоит из условий, предзагрузки и действий: изменение полей, сохранение в CRM, запуск ведомых процессов. Главное отличие от AOW — контекст с переменными.

Редактор процесса. Справа — переменные: запись из события, загруженные из CRM и созданные в процессе.
Переменные и предзагрузка. Кроме object (записи из события), процесс может загрузить из CRM любые записи в именованные переменные. Ищет по id или по любому полю. Значение для поиска берётся из поля объекта, из поля другой переменной или задаётся вручную. Если запись не нашлась, переменная равна null: это не ошибка, и на это можно поставить условие.
Цепочки. Источник «Из поля переменной» позволяет идти от записи к записи. Отношение в CRM для этого не нужно:
«preload»: [ {«var»: «account», «source»: «object_field», «module»: «Accounts», «by_field»: «id», «value»: «object.account_id»}, {«var»: «manager», «source»: «variable_field», «module»: «Users», «by_field»: «id», «value»: «account.assigned_user_id»} ]
Сравнение объекта с переменными. Условие сравнивает поле не только с константой, но и с полем другой переменной. Например, «ответственный обращения — не менеджер аккаунта»:
{«field»: «object.assigned_user_id», «operator»: «not_equals», «value_type»: «from_object_field», «value»: «manager.id»}
Предзагрузка ленивая. Сначала проверяются условия по самой записи. Если они уже ложны, в CRM нет ни одного запроса.
Результат из всех данных. Выражения (concat, upper, lower, trim, len, substr, round, abs, min, max, арифметика) могут ссылаться на любое поле любой переменной. Изменять и сохранять можно не только запись из события, но и любую переменную. Новая переменная, сохранённая в CRM, становится новой записью. В CRM уходят только изменённые поля: PUT для существующей записи, POST для новой.
{«type»: «modify», «target»: «object.description», «change_type»: «calculate», «save_to_crm»: true, «value»: «concat(‘Escalated to ‘, manager.first_name, ‘ ‘, manager.last_name, ‘ (‘, account.name, ‘, ‘, account.industry, ‘)’)»}

Реальное выполнение на SuiteCRM 8: аккаунт → менеджер → переназначение и описание → запуск ведомого процесса.
Ведомые процессы
Действие «Запуск следующего процесса» стартует ведомые процессы. Это то, чего больше всего не хватало в AOW:
-
декомпозиция и переиспользование. «Эскалировать обращение», «создать задачу менеджеру», «создать звонок» — отдельные процессы, и один и тот же можно вызывать из разных родителей;
-
наследование контекста. Ведомый процесс получает копию события и всех переменных родителя, поэтому загруженное родителем не нужно загружать снова;
-
своя логика. У ведомого процесса свои условия, предзагрузка и однократный запуск;
-
параллельность и изоляция. Ведомые процессы запускаются асинхронно в отдельных корутинах. Сбой или повторы в одном не влияют ни на родителя, ни на соседей. У каждого своё выполнение и аудит со ссылкой на родителя;
-
безопасность. Глубина вложенности ограничена (по умолчанию 10), циклы A → B → A обнаруживаются при сохранении процесса. Неактивный ведомый процесс пропускается, поэтому часть сценария можно выключить, не трогая родителя.
Надёжность и аудит
-
Повторы. Ошибки CRM API делятся на восстановимые (5xx, сетевые) и невосстановимые (4xx, ошибки конфигурации). Восстановимые повторяются: по умолчанию 3 раза, с экспоненциальной задержкой.
-
Восстановление. Состояние выполнения сохраняется после каждого шага. Если backend упал посреди процесса, фоновая задача заберёт зависшее сообщение стрима и продолжит с нужного шага. Сообщение, которое не удалось обработать пять раз, уходит в очередь недоставленных crm_events:dlq.
-
Идемпотентность. Повторная доставка того же события не запускает процесс второй раз.
-
Однократный запуск для записи. Процесс выполнится для записи не больше одного раза, даже если её сохраняют снова — в том числе сам процесс, когда пишет в CRM. Это защищает от зацикливания. Отметку можно сбросить из интерфейса.
-
Аудит шага: запрос, результат, ошибка, длительность, попытки, плюс снимок контекста: событие, переменные, разбор условий.

CRM ответила HTTP 500: три повтора, затем ошибка, следующий шаг пропущен. Текст ошибки — прямо в аудите.
Для эксплуатации есть /api/health (БД, оба Redis, отставание стрима) и метрики Prometheus на /metrics. Процесс можно запустить вручную с тестовым объектом, а мок-CRM в комплекте позволяет разрабатывать процессы без настоящей CRM.
Плагины для SuiteCRM 7 и 8
Плагины не трогают ядро SuiteCRM: всё ставится в custom/ как обычный пакет Module Loader. Его видно в Admin → Module Loader, и оттуда же его можно удалить.
-
SuiteCRM 7.14 — плагин в корне CRM. Rewrite /api/v9 ставится в vhost или в начало .htaccess; штатная пересборка .htaccess его сохраняет.
-
SuiteCRM 8.10 — плагин в legacy-слое public/legacy. Logic hooks там срабатывают и при сохранении из нового Angular-интерфейса. Rewrite ставится в vhost раньше front controller Symfony, после установки очищается кэш.
Ставить можно скриптом — в CRM на хосте или в Docker-контейнере — или zip-архивом через Module Loader:
git clone https://github.com/madarch-labs/WPS_SuiteCRM8_plugin && cd WPS_SuiteCRM8_plugin cp config/wps.env.example config/wps.env # адрес Redis WPS и WPS_API_STATIC_KEY bin/install.sh —crm-path /var/www/html —apache-conf /etc/apache2/sites-available/000-default.conf # … GET /api/v9/modules: valid key → 200, wrong key → 401 # OK: the WPS API of the CRM answers
Скрипт проверяет версию CRM и права, пишет настройки, ставит пакет, добавляет rewrite и в конце сам проверяет API. Повторный запуск безопасен и работает как обновление. Есть bin/uninstall.sh: поле-маркер с данными и настройки можно сохранить или удалить.

Плагин в Admin → Module Loader SuiteCRM 8.

Результат: лид переведён в In Process, звонок и задачу создали два ведомых процесса.
Быстрый старт
git clone https://github.com/madarch-labs/Workflow-Processing-Service && cd Workflow-Processing-Service # режим мок-CRM: в ./.env # CRM_API_BASE_URL=http://mock:9502 # CRM_API_STATIC_KEY=mySecretKey # MOCK_SERVICE_URL=http://mock:9502 docker compose up -d —build curl -s http://localhost:9501/api/health curl -sg «http://localhost:9502/test-event?module=Deals&set[status]=Active» # тестовое событие
Интерфейс откроется на порту из WPS_UI_PORT (по умолчанию 3000). Вход — admin@wps.local / admin; пароль смените сразу. Документация на русском и английском лежит в doc/ репозитория: руководство пользователя, эксплуатация, архитектура, справочник API, руководство разработчика.
Ограничения
-
Операторов сравнения сейчас четыре: равно, не равно, содержит, не содержит. В AOW их больше.
-
Отправки писем нет.
-
Нужна своя инфраструктура: сервис, PostgreSQL и два Redis.
Всё это расширяется. У движка есть реестры с интерфейсами для новых типов действий, операторов, типов изменений, стратегий загрузки, функций выражений, эндпоинтов, экранов и языков. На каждый — пошаговая инструкция с проверенным примером.
Присоединяйтесь
Если вы работаете с SuiteCRM и упираетесь в возможности AOW — попробуйте WPS и расскажите, чего не хватает.
-
⭐ Поставьте звёздочку репозиториям: сервис, плагин SuiteCRM 7, плагин SuiteCRM 8 — так проект увидит больше людей.
-
🐛 Пишите в Issues о багах и сценариях, которые не получается собрать.
-
🛠 Присылайте pull request’ы: новые операторы («больше», «меньше», «пусто»), отправка писем, адаптеры для других CRM, переводы интерфейса. Точки расширения и порядок работы описаны в руководстве разработчика.
Лицензия — MIT: используйте, меняйте и встраивайте в свои проекты.
