WPS: АСИНХРОННЫЕ БИЗНЕС-ПРОЦЕССЫ ДЛЯ SUITECRM 7 И 8 ВМЕСТО ВСТРОЕННОГО AOW WORKFLOW

Мы выложили в open source под лицензией MIT Workflow Processing Service (WPS) — отдельный сервис для бизнес-процессов над записями CRM. Вместе с ним вышли два плагина: для SuiteCRM 7 и для SuiteCRM 8. В статье — какие проблемы встроенного движка процессов SuiteCRM мы решали, как устроен WPS и как подключить его к своей CRM.

Что не так со встроенными процессами SuiteCRM

В SuiteCRM есть модуль WorkFlow (AOW). Для простых правил вида «при сохранении сделки поменять поле» его хватает. Когда сценарии усложняются, начинаются ограничения. Мы разобрали код модуля, и вот что видно.

Модуль WorkFlow (AOW) в SuiteCRM
Модуль WorkFlow (AOW) в SuiteCRM

 Встроенный AOW: условия и фиксированный набор действий.

  1. Логика выполняется внутри сохранения. AOW подключён к logic hook after_save. Процесс с режимом «при сохранении» выполняет все действия прямо в запросе пользователя: человек нажал «Сохранить» и ждёт, пока создадутся записи и уйдут письма. Ошибка в действии становится ошибкой сохранения. Альтернатива — запуск по cron, но тогда реакция откладывается.

  2. Процесс видит одну запись. Доступны поля сохранённой записи и записей, связанных с ней отношениями CRM. Найти запись по произвольному полю (по имени, email, внешнему коду) или пройти цепочку «обращение → аккаунт → менеджер аккаунта» нельзя.

  3. Нет композиции. Процесс не может запустить другой процесс и передать ему данные. Цепочки получаются только неявно: процесс меняет запись, её сохранение запускает следующий workflow. Связи в аудите при этом нет, контроля циклов тоже.

  4. Скудный аудит и нет повторов. В Process Audit видны запись и статус Complete или Failed. Текста ошибки, запроса и ответа там нет, политики повторов для временных сбоев тоже нет.

  5. Нельзя проверить процесс без настоящих данных — ни ручного запуска с тестовым объектом, ни песочницы.

Как устроен WPS

Идея простая: CRM только сообщает о событии, а вся логика живёт в отдельном сервисе.

Архитектура WPS
Архитектура WPS

 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 и повторы
Ошибка 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: поле-маркер с данными и настройки можно сохранить или удалить.

Пакет в Module Loader SuiteCRM 8
Пакет в Module Loader SuiteCRM 8

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

Лид в SuiteCRM 8 после процесса
Лид в 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: используйте, меняйте и встраивайте в свои проекты.

Комментарии (0)