Рано или поздно у любой ИТ компании (аутсорс или продуктовой) возникает желание организовать собственное пространство, где можно хранить информацию по проектам, сотрудникам, продажам. Вести рабочую переписку и обсуждать задачи/стратегии/документы. Чаще всего, такие компании начинают кодить все сами или пилят что-то для Битрикс24 и тд. В данной серии статей я расскажу о нашем велосипеде — опыте автоматизации процессов. Как положено, почти все self-hosted, opensource и постараемся обойтись почти без кодинга.


Disclamer


В серии статей описан пример реализации инфраструктуры, автор не призывает повторять и не претендует на "правильность" таких подходов. Некоторые части описываемой системы реально и успешно используются в нескольких организациях на протяжении 3-х лет. Автор с радостью принимает предложения по улучшению системы или предложения альтернативных решений. Пожалуйста, не разводите обсуждения типа "кому это нужно" и "что за костыли", кому не нужно, пусть не читает и не мешает комментаторам вести диалог

Итак, что же может захотеть средняя аутсорс компания от подобной системы:


  1. База данных сотрудников с распределением прав доступа к информации
  2. Конечно GIT сервер, пользователи которого, будут связанны с пунктом 1
  3. Обязательно Chat-Server (Messenger если хотите) и конечно с пользователями из пункта 1
  4. Корпоративная почта, по любому с пользователями из пункта 1
  5. Организация процесса найма/подбора кандидатов для HR
  6. Трекинг проектов и задач
  7. Организация процесса продаж
  8. Организация процесса обучения и стажировок
  9. Роад-мап разработчиков и других сотрудников
  10. Геймификация некоторых процессов (для души)

– это наш скромный список. У кого-то он в разы больше, у кого-то наоборот.



Сразу возникает много вопросов, "зачем нам свой GIT сервер?", "зачем сервер чатов?" и тд. На эти и новые вопросы из комментарий я буду отвечать по мере написания статей по конкретному направлению.


Из схемы перед катом и списка выше сразу понятно, что основной частью системы будет являться подсистема управления пользователями. Исторически, да и на практике, LDAP в этих вопросах лидер. LDAP старый, сложный, но очень мощный. Я предпринимал 3 попытки затащить LDAP в разных реализациях типа OpenLDAP и других, более легковесных, но это отнимало много времени, и я решил пока обойтись без него.


Таким образом, нам придется управлять всеми действиями через RESTapi. Возможно, позже я еще раз попробую интегрировать LDAP и напишу дополнение.


В качестве основы всей системы, мы перепробовали множество OpenSource ERP/CRM систем (Odoo, Axellor и другие), пытались адаптировать OpenProject и подобные проекты. Все они по своему хороши, но одним из требований была легковесность и простота доработки. Решили выбрать что-то на PHP, благо их вагон.


И так, мы имеем EspoCRM. Штука молодая, сложно сказать что это "самый лучший" выбор, но она нам понравилась. Без кодинга можно создавать сущности, можно добавлять поля и связи, похожа на CMS, но чуть более расширенная.


Наконец к делу. Подготавливаем платформу


После долгого предисловия, Espo будет выполнять следующие задачи:


  1. Управление пользователями
  2. Портал для сотрудников с новостями и другой информацией
  3. Организация HR процесса
  4. Ведение стажеров и обучающихся
  5. Таск-трекер
  6. Организация процесса продаж (CRM все таки :))

Устанавливаем платформу


Тут все как с любым PHP проектом. Весь процесс подробно и с картинками расписано вот тут


После установки мы попадаем вот в такой интерфейс:



И мы имеем готовую к работе CRM. Но нас сейчас интересует управление пользователями.
(Я решил использовать Русскую версию, перевод неплохой и понятный).


Идем в Администрирование -> Управление объектами -> User (Поля).
Видим очень много разных полей. Сейчас страница пользователя (для админа) выглядит вот так:



В зависимости от хотелок, можно добавить такие поля как Skype, Telegram, VK, Facebook и тд. Я добавил День рождения (странно, что его нет по дефолту), Дата начала стажировки, Дата начала обучения, Дата приема на работу, ИНН, Банк и номер расчетного счета (для документов и бухгалтера).


Мы также можем настроить видимость полей, в зависимости от роли или от любого другого поля.
Поля можно добавлять/удалять в любой момент.


Теперь идем в Администрирование -> Управление макетами -> Пользователи -> Детализация.


Видим что-то такое:



Теперь просто мышкой перетягиваем новые поля в нужные места, создаем панели и тд, все по своему вкусу. Сохраняем, и теперь вид страницы пользователя будет как в макете.
На этом настройку полей и шаблонов мы закончим, далее по тексту я буду писать что-то типа "добавим поле" и тд.


Это не мануал по EspoCRM, и я никакого отношения к этому продукту не имею. Я лишь описываю процесс создания интрасети, поэтому что-то описано более подробно, что-то наоборот.

Эту систему можно настраивать долго и беспощадно, можно менять внешний вид и тд. Но сейчас перейдем к следующей подсистеме.


Почта


Почта — крайне важный компонент системы. Ваши коллеги не обязательно должны ею активно пользоваться, есть мессенджеры и все такое. Но регистрации в разных сервисах, деловая переписка и тд — должно происходить через email.


В нашем случае — почта это не self-hosted подсистема. Конечно, можно организовать свой сервис, куча готовых решений на рынке. Но мы решили использовать Яндекс.Коннект. У них очень добрые лимиты в 1000 пользователей, достойный web-интерфейс и достаточный RESTapi. Конечно не без рекламы, но в наше время — это нормально.


Организовать почту можно по инструкциям от производителя и затем вернуться к этой статье.


Будем считать, что у нас уже есть настроенный сервис, и теперь нам надо связать почту и портал на espo.


Так как мы в целом не хотим много кодить, и интеграций у нас в будущем будет больше или они могут изменяться, мы решили использовать Node-RED.


Node-RED


Некоторым покажется странным использование такой штуки, но это действительно крутой продукт, позволяющий сделать очень много интеграций за короткий промежуток времени.
У Node-RED конечно есть минусы, основные: отсутствие разграничения прав, и он не подходит для высоко-нагруженных систем. Последнее нас не волнует, так как система не будет нагруженной, а вот первое означает, что изменять конфигурацию системы сможет только тот, у кого есть логин и пароль от админки.


Ставим Node-RED по инструкции с оф. сайта, проверяем, что все работает и открываем доки на API Яндекс.Коннекта.


Нам нужно зарегистрировать новое приложение и сгенерировать ему API Token.


В платформах выбираем "Веб-сервисы" и нажимаем кнопку-ссылку "Подставить URL для разработки".



Далее, настраиваем права данному приложению.


Нас сейчас интересует только Яндекс.Коннект Directory API, и настроить права можно, к примеру, вот так:



(минимально необходимо только Управление пользователями и Чтение данных о сотрудниках)


Сохраняем и видим что-то вот такое:



Теперь нам нужно получить токен, с которым и будем делать запросы. Добрые ребята из Яндекса предусмотрели Отладочный токен, который очень легко получить и можно смело использовать для запросов.
Открываем в браузере ссылку https://oauth.yandex.ru/authorize?response_type=token&client_id=<ID>, разрешаем доступ приложению и получаем заветный токен



Сохраняем токен в надежном месте.


Теперь, наконец, переходим к интеграции через Node-RED.


  • Открываем http://localhost:1880, видим Flow 1
  • Перетаскиваем из палитры inject и function
  • Связываем как на картинке ниже
  • Открываем настройки inject и ставим галочку "Inject once after 0.1 seconds, then"
  • Открываем function и пишем такой код

global.set('YaConnectToken', 'AgAAAA......');
return msg;

Так мы запишем токен в глобальные переменные и дальнейшем сможем получать его с помощью global.get() в любой функции.


Inject нужен, чтобы заставить функцию выполнится при старте Flow 1. В эту же функцию, мы будем добавлять и другие токены. Не очень удобно, но лучшего варианта я пока не нашел


Node-RED + Яндекс.Коннект


  • Кидаем на Flow вот такие ноды: inject, function, http request
  • Соединяем в том же порядке
  • Открываем inject, выбираем в выпадающем меню, слева от поля ввода, JSON и пишем вот такой текст
    {
    "userName":"testuser",
    "firstName":"Test",
    "lastName":"User",
    "emailAddress":"testuser@mydomain.ru",
    "dob":"1988-01-01",
    "gender":"male",
    "passwordConfirm":"12345678Eiru",
    "isActive":true
    }

Это будет тестовый аккаунт нашего потенциального юзера.


  • Открываем ноду function и пишем следующий код:
    const TOKEN = global.get('YaConnectToken');
    const user = msg.payload; // Получаем данные о юзере из предыдущей ноды
    const body = {
    'department_id': 1, // ID отдела в Яндекс.Коннект, отдел по умолчанию 1
    'is_admin': false, // Новый пользователь не админ
    'nickname': user.userName,
    'name': {
    'first': user.firstName,
    'last': user.lastName
    },
    'birthday': user.dob, // 'YYYY-MM-DD',
    'gender': user.gender, //'male/female/null'
    'password': user.passwordConfirm,
    'is_dismissed': user.isActive,
    'position': user.title // Опционально
    }
    return {
    headers: {
    'Authorization': 'OAuth ' + TOKEN,
    //'X-Org-ID': 1234 // Нужно если у Вас больше одной организации. Подробнее тут: https://wilix.org/l/wlwrtj
    },
    payload: body
    }

Тут мы подготавливаем тело запроса по инструкции и устанавливаем заголовок с токеном авторизации.


  • Открываем ноду http request, выбираем тип запроса POST, и в url вставляем такой адрес https://api.directory.yandex.net/v6/users/


  • Нажимаем кнопку Deploy в правом верхнем углу и тыкаем на вкладыш слева от нового inject (это заставит ноду отправить JSON из inject в function и запустит весь процесс)


  • Если все прошло удачно, на выходе http request ноды, мы получим msg со statusCode 200 или 201 и примерно такой payload:



Ура! Мы сделали первый кусок интеграции!


Интеграция EspoCRM с Node-RED


Теперь нам нужно наконец связать базу пользователей (нашу CRM) с Node-RED и в последствии с Яндексом.
Тут от нас потребуется немного PHP, но я уже все подготовил.
Достаточно создать файл UserSaved.php в директории EspoCRM по пути custom/Espo/Custom/Hooks/User и скопировать туда вот этот код:


<?php
namespace Espo\Custom\Hooks\User;

use Espo\ORM\Entity;

class UserSaved extends \Espo\Core\Hooks\Base {
  public function afterSave(Entity $entity, array $options = []) {
    $entityValues = $entity->getValues();
    unset($entityValues['password']);
    $entityValues['isNew'] = $entity->isNew();
    $data = array(
      'event' => 'afterSave',
      'entity' => $entityValues,
    );
    $this->_callRed($data);
  }

  public function afterRemove(Entity $entity, array $options = []) {
    $entityValues = $entity->getValues();
    unset($entityValues['password']);
    $data = array(
      'event' => 'afterRemove',
      'entity' => $entityValues
    );
    $this->_callRed($data);
  }

  private function _callRed($data) {
    $data_string = json_encode($data);
    $ch = curl_init('__NODE_RED_ENDPOINT__');
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        'Content-Type: application/json',
        'Content-Length: ' . strlen($data_string))
    );
    $response = curl_exec($ch);
    curl_close($ch);
    return $response;
  }
}

Тут мы объявляем Hook на сохранение и удаление объекта пользователя. (можно почитать в доке) Каждый раз, когда кто-то сохраняет/создает/удаляет пользователя в Espo, будет отправляться запрос на сервер Node-RED. Наверное не самый безопасный способ, но если сервер Node-RED и EspoCRM находятся на одной физической (или виртуальной) машине, можно использовать localhost и уже будет чуть секурнее.
NODE_RED_ENDPOINT — мы настроим чуть позже.


  • Переключаемся обратно на Node-RED и перетягиваем из палитры следующие ноды: http in, http response, debug
  • Открываем http in и настраиваем как на картинке
  • Соединяем ноды вот так
  • Возвращаемся к файлу UserSaved.php и меняем NODE_RED_ENDPOINT на АДРЕС_NODE_RED/user-event
  • Сохраняем файл
  • Нажимаем Deploy в Node-RED
  • В EspoCRM идем в Администрирование -> Очистить кэш
  • В EspoCRM, в левой панели открываем "Пользователи" и создаем нового юзера
  • Если все прошло правильно, создастся новый юзер и в консоли Node-RED (справа нажать на вкладку с жуком) мы увидим подобный JSON
    {
    id: "5e430b3c59783cb41"
    name: "User Test"
    deleted: false
    isAdmin: false
    userName: "usertest"
    type: "regular"
    password: "GVhrB......"
    passwordConfirm: "oRM1..."
    authMethod: null
    salutationName: "Mr."
    firstName: "User"
    lastName: "Test"
    isActive: true
    isPortalUser: false
    isSuperAdmin: false
    title: "Frontend developer"
    emailAddress: null
    phoneNumber: null
    sendAccessInfo: false
    gender: "Male"
    createdAt: "2020-02-11 20:14:52"
    modifiedAt: "2020-02-11 20:14:52"
    dob: null
    inn: null
    emailAddressIsOptedOut: null
    phoneNumberIsOptedOut: null
    emailAddressData: array[0]
    phoneNumberData: array[0]
    defaultTeamId: null
    defaultTeamName: null
    teamsIds: array[0]
    teamsNames: object
    teamsColumns: object
    rolesIds: array[0]
    rolesNames: object
    portalsIds: array[0]
    portalsNames: object
    portalRolesIds: array[0]
    portalRolesNames: object
    createdById: "1"
    isNew: true
    }

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


Осталось все это соединить:


  • Переделываем схему как на картинке (самые первые 2 ноды не трогаем)
  • Открываем ноду function и меняем строку 2 на вот это
    const user = msg.payload.entity;
    if (msg.payload.event != 'afterSave') { // Если событие не про сохранение пользователя, ничего не делать
    return null
    }
  • Нажимаем Deploy
  • Идем в Espo и опять создаем нового юзера (предыдущего можно удалить)
  • Если все хорошо, то в консоли Node-RED видим JSON от Яндекса с данными нового аккаунта
  • Радуемся :)

Итог


Мы рассмотрели базовый принцип интеграции через Node-RED. Конечно, мы не учли удаление и редактирование пользователя. Ниже ссылки на готовый Flow, который все учитывает. Его можно просто скопировать и импортировать себе (обновив токены доступа конечно).


EspoCRM можно настроить под свой вкус, цвет и наладить разные процессы, но вместо нее можно использовать любую другую систему, которую вы готовы взять за основу.


В следующих статьях я расскажу:


  • как прикрутить self-hosted GIT (не GitLab :))
  • как прикрутить self-hosted чат аля Slack
  • как мы организовали процесс подбора персонала для HR и как ловим и сохраняем лидов с сайта.
  • и как это все синхронизируется с базой пользователей.

FAQ:
Q: Зачем вообще Node-RED, если запрос можно сделать прямо из PHP к Яндексу?
A: Имея интеграцию на стороне Node-RED, мы можем менять конфигурацию, добавлять новые сервисы и хуки, на изменения пользователя, без дополнительного кодинга (не считая мелких функций на Node-RED). Процесс деплоя обновлений сводится к одной кнопке.