Всё описанное ниже сделано на 1С УПП 1.3 + CRM, редакция 1.4, но всё это можно реализовать в любой конфигурации.
Разработчики из 1С-Коннект добавили новый механизм под названием "Трансляция событий". Этот новый механизм мы сразу же начали использовать после того как получили доступ к Бета-тестированию, т.к. для решения наших задач он оказался идеальным, будто именно для нас и разработан. В нём есть очень интересная и полезная функция для нас, а именно отправлять сообщения из 1С Предприятие в 1С-Коннект без всяких внешних компонент, как было раньше.
Трансляция событий представляет собой механизм, дополняющий основные команды по манипулированию данными, и предоставляющий информирование об изменении/добавлении данных на стороне 1С-Коннект, внешнему пользователю API (Документация "Трансляция событий").
Задача
У нас была задача, где надо было автоматизировать работу менеджера, которая заключалась в том, чтобы при получении определенных электронных писем об оплате создавать обращение в 1С-Коннекте в свободную линию поддержки (ЛП) специалиста, что клиент всё оплатил и нужно отгрузить товар. Всё это контролировал, ответственный за это дело менеджер, искал свободную ЛП и писал туда заявку менеджеру. Почему мы использовали чат вместо «тикетов»? В чате менеджеры могут писать доп. информацию и ввести переписку по текущей задаче или же смотреть историю переписки и обращении.
Разработка решения задачи.
Сначала для решения этой задачи была разработана обработка, которая после запуска должна была каждые 15 минут обрабатывать электронные письма и писать сообщение в 1С-Коннект. Работала обработка через Agent API (Что такое Agent API и его документация), т.к. раньше программно отправлять сообщение можно было только через Agent API , как нам было известно. Для этого необходимо было подключить внешнюю компоненту, которая отправляла команды через канал Named-Pipe (Подробнее о том, что такое Named-Pipe). У этого способа были такие минусы:
- Работает только через клиент
- 1С-Коннект и 1С должны быть всегда включены
- Нельзя отправлять сообщение от имени другого пользователя, а только от авторизованного в 1С-Коннекте
- Сложность в интеграции
Эти ограничения мешали нормальной работе обработки, нужно было выделить ПК, который должен был быть включённым 24/7, с запущенной программой 1С-Коннект и с запущенным 1С Предприятие, нужно было проверять всё ли работает в штатном состоянии.
После обращения с этой проблемой в тех. поддержку 1С-Коннекта нам предложили поучаствовать в бета-тестировании нового механизма, которая могла нам помочь. Мы с радостью согласились и когда настал день, когда предоставили доступ мы были восхищены тем, что это как раз то, что нам нужно.
Новый механизм избавил нас от ограничении Agent API и получил такие плюсы:
- Работает через сервер и нет зависимости от самой программы 1С-Коннект
- Можно программно отправлять сообщения от имени любого специалиста вашей компании
- Можно отправлять файлы
- Можно переводить обращение
- Закрывать обращения
- Не требует сложного программирования
- Быстро возвращает, в указанный URL, информацию о всех действиях 1С-Коннекта, с подробным описанием.
Обработку переделали под новый механизм и теперь он стал работал как "часы" без всяких ограничении.
Обработка запускается фоновым заданием каждые 5 минут, после запуска обработка собирает данные по электронным письмам, какой контрагент, когда, сколько, по какому заказу и за что оплатил. Далее с помощью "Трансляция событий" получает список занятых ЛП и сохраняет в табличной части (ТЧ) «ТЧОктрытыеЗаявки». Для этого устанавливаем HTTPS соединение, отправляем GET запрос (Описание запроса в документации), получаем результат и обрабатываем. Для подключения HTTPS соединения используем такой же логин, пароль как и для WebAPI.
Отмечу, что почти везде POST запросы.
Процедура ЗагрузитьОткрытыеЗаявки() ТЧОткрытыеЗаявки.Очистить(); Сервер = "push.1c-connect.com"; ТекстЗапрос = "/v1/line/treatment/"; Логин = Константы.бфЛогин.Получить(); //логин и пароль для доступа такой же как и для WebApi Пароль = Константы.бфПароль.Получить(); ssl1 = Новый ЗащищенноеСоединениеOpenSSL( Новый СертификатКлиентаWindows(), Новый СертификатыУдостоверяющихЦентровWindows()); Соединение = Новый HTTPСоединение(Сервер,443,Логин,Пароль,,,ssl1); Заголовки = Новый Соответствие; Заголовки.Вставить("Content-Type", "application/json"); Запрос = Новый HTTPЗапрос(ТекстЗапрос,Заголовки); Попытка Результат = Соединение.Получить(Запрос); Исключение ЗаписьЖурналаРегистрации("Создать заявки менеджерамв Коннекте. " + ОписаниеОшибки(), УровеньЖурналаРегистрации.Ошибка); КонецПопытки; Если Результат.КодСостояния <> 200 Тогда Отказ = Истина; Возврат; КонецЕсли; ЧтениеJSON = Новый ЧтениеJSON; ЧтениеJSON.УстановитьСтроку(Результат.ПолучитьТелоКакСтроку()); РезультатДерево = Неопределено; СформироватьДерево(ЧтениеJSON, РезультатДерево); Для Каждого Строка Из РезультатДерево Цикл line_id = СокрЛП(Строка.Получить("line_id")); НВСтр = ТЧОткрытыеЗаявки.Добавить(); НВСтр.ServiceID = line_id; КонецЦикла; КонецПроцедуры
Далее получаем список всех ЛП, которые хранятся в регистре сведении бфВидыСервисов и отсекаем, которые уже заняты.
Циклом проходим по ТЧ с данными об электронных письмах, создавая на каждое эл. письмо обращение в свободное ЛП с помощью бота "Трансляция событий". Если свободных ЛП нет, то ответственному менеджеру отправляется на почту письмо, что все его ЛП заняты с информацией о заявке и об основном менеджере, который должен был принять заявку. У каждого контрагента свой основной менеджер.
ЗагрузитьОткрытыеЗаявки(); Для каждого Строка Из ТЧ Цикл Если Строка.Различаются или НЕ Строка.Обработать или Строка.ЗаявкаСоздана Тогда Продолжить; КонецЕсли; // У каждого менеджера свои ЛП, найдём ЛП менеджера //ТЧСервисы с колонками "IDСервиса, ИмяСервиса, Менеджер" Найденные = ТЧСервисы.НайтиСтроки(Новый Структура("Менеджер", Строка.ОсновнойМенеджер)); Если Найденные.Количество() > 0 Тогда IDЛП = ПолучитьСвобнуюЛП(Найденные[0].IDСервиса, Строка.ОсновнойМенеджер); //Получаем ИД свободной ЛП Если ЗначениеЗаполнено(IDЛП) Тогда ТекстСообщения = ПолучитьТекстСообщения(Строка); //Формируем текст сообщения // Отправляется сообщение в указанный ЛП от имени Специалиста, тем самым создаёт обращение РезультатОбращение = бфСервер.НаписатьСообщениеВКоннектЧерезПушАпи(IDЛП, АйдиКлиента, ТекстСообщения, АйдиСпец); Если РезультатОбращение Тогда //Записываем в регистр информацию о том, что обращение создано и доп информацию ДействияСРегистром(Строка.ЭлПисьмо,,Строка.ЗаказКлиента,IDЛП, , Строка.Контрагент); КонецЕсли; УдалитьЛПИзТЧ(IDЛП); //Удалить ЛП из ТЧСервисы, чтобы туда больше не создавались заявки, т.к. уже занято КонецЕсли; Иначе СообщениеОтправлено = ДействияСРегистром(Строка.ЭлПисьмо, 4,,,,Строка.Контрагент); Если НЕ СообщениеОтправлено Тогда Попытка //Отправляем на Эл. почту письмо менеджеру, с описанием заявки о том, что все ЛП Заняты ИсхПисьмо = ОтправитьСообщениеНаПочту(Строка.ОсновнойМенеджер, Строка.ЭлПисьмо, 1); //Записываем в регистр информацию о письме ДействияСРегистром(Строка.ЭлПисьмо, 5, ИсхПисьмо,,,Строка.Контрагент); Исключение КонецПопытки; КонецЕсли; КонецЕсли; КонецЦикла;
Но перед тем как всё это работало надо боту один раз подключить каждую ЛП, по которым будут отправляться сообщения. Для этого отправляем POST запрос (Описание запроса в документации) со структурой обязательных параметров:
- Тип = бот
- URL адрес опубликованной базы, куда будет приходить ответ
- id линии поддержки, к которому нужно подключить бота
Функция ПодключитьЛинию(Соединение, IDЛП) ТекстЗапрос = "/v1/hook/"; Заголовки = Новый Соответствие; Заголовки.Вставить("Content-Type", "application/json"); Запрос = Новый HTTPЗапрос(ТЕкстЗапрос,Заголовки); ЗаписьJSON = Новый ЗаписьJSON; ЗаписьJSON.УстановитьСтроку(); СтруктураЗапроса = Новый Структура; СтруктураЗапроса.Вставить("type", "bot"); СтруктураЗапроса.Вставить("url", "https://push.example.com/connect/hook/"); СтруктураЗапроса.Вставить("id", IDЛП); ЗаписатьJSON(ЗаписьJSON, СтруктураЗапроса); ТелоЗапроса = ЗаписьJSON.Закрыть(); Запрос.УстановитьТелоИзСтроки(ТелоЗапроса,КодировкаТекста.UTF8,ИспользованиеByteOrderMark.НеИспользовать); Попытка Результат = Соединение.ОтправитьДляОбработки(Запрос); Исключение ЗаписьЖурналаРегистрации("Создать заявки менеджерамв Коннекте. " + ОписаниеОшибки(), УровеньЖурналаРегистрации.Ошибка); КонецПопытки; Если Результат.КодСостояния <> 200 Тогда Возврат Ложь; КонецЕсли; Возврат Истина; КонецФункции // ПодключитьЛинию()
А вот как открывать обращение (Описание запроса в документации)
Post запрос с параметрами
- id линии поддержки
- текст сообщения
- id пользователя, который получит сообщение
- id специалиста, от имени которого отправляется сообщение
Ещё можно добавить параметр «keyboard», который добавляет кнопки пользователю для быстрого ответа, он необязательный.
Функция ОткрытьОбращениеПушАпи(Соединение,АйдиЛинии,АйдиКлиента,ТекстСообщения, АйдиСпец) ТекстЗапрос = "/v1/line/send/message/"; Заголовки = Новый Соответствие; Заголовки.Вставить("Content-Type", "application/json"); ЗаписьJSON = Новый ЗаписьJSON; ЗаписьJSON.УстановитьСтроку(); СтруктураЗапроса = Новый Структура; СтруктураЗапроса.Вставить("line_id", АйдиЛинии); СтруктураЗапроса.Вставить("text", ТекстСообщения); СтруктураЗапроса.Вставить("user_id", АйдиКлиента); СтруктураЗапроса.Вставить("author_id", АйдиСпец); ЗаписатьJSON(ЗаписьJSON, СтруктураЗапроса); ТелоЗапроса = ЗаписьJSON.Закрыть(); Запрос = Новый HTTPЗапрос(ТекстЗапрос,Заголовки); Запрос.УстановитьТелоИзСтроки(ТелоЗапроса,КодировкаТекста.UTF8,ИспользованиеByteOrderMark.НеИспользовать); Попытка Результат = Соединение.ОтправитьДляОбработки(Запрос); Исключение #Если клиент Тогда Сообщить(ОписаниеОшибки()); #КонецЕсли КонецПопытки; Если Результат.КодСостояния <> 200 Тогда Сообщить("Код состояния " + Результат.КодСостояния); Возврат Ложь; КонецЕсли; Возврат Истина; КонецФункции
Записываем в регистр информацию о созданной заявке, чтобы потом видеть, что по такой-то электронной письме создано обращение. Измерение Объект - это "Электронное письмо"
Описание всех событии приходит на указанный URL адрес, например, в нашем случае это опубликованная база, где используется HTTP-сервис для получения ответа от "Трансляция событий". Хочу добавить, что ответ мы получаем быстро и оперативно обо всех событиях 1С-Коненкта.
Также мы добавили кнопку "Написать в 1С-Коннект" в формы контрагента, конт. лица контрагента, CRM_РабочийСтол и в документ Событие, которая работает через "Трансляция событий".
При нажатии на кнопку выводится список доступных и подключённых у конт. лица ЛП, после выбора ЛП выводится окно ввода сообщения, а дальше сообщение отправляется
Процедура КоманднаяПанельКонтактныеЛицаКонтрагентаНаписатьВКоннект(Кнопка) КонтактноеЛицо = ЭлементыФормы.КонтактныеЛицаКонтрагента.ТекущаяСтрока; Если НЕ ЗначениеЗаполнено(КонтактноеЛицо) Тогда Предупреждение("Контактное лицо не выбрано!"); Возврат; КонецЕсли; СписокСервсиов = бфСервер.ПолучитьСписокДоступныхЛППоКонтЛицу(КонтактноеЛицо); Если НЕ СписокСервсиов.Количество() > 0 Тогда Сообщить("У тек. пользователя нет доступных сервсиов для взаимодействия с клиентом"); Возврат; КонецЕсли; Попытка Если СписокСервсиов.Количество() > 1 Тогда ВыбранноеЗначение = ВыбратьИзСписка(СписокСервсиов); Иначе ВыбранноеЗначение = СписокСервсиов[0]; КонецЕсли; СервисИД = СокрЛП(ВыбранноеЗначение.Значение); бфСервер.НаписатьСообщениеВКоннект(КонтактноеЛицо,СервисИД, Ссылка); Исключение КонецПопытки; КонецПроцедуры //В общем модуле бфСервер Функция НаписатьСообщениеВКоннект(КонтактноеЛицо, СервисИД, Контрагент = Неопределено) Экспорт Результат = Ложь; Если Контрагент = Неопределено Тогда Контрагент = КонтактноеЛицо.Владелец; КонецЕсли; АйдиКонтрагента = ПолучитьАйдиКонтрагента(Контрагент); АйдиКлиента = ПолучитьАйдиПользователяКонтрагента(КонтактноеЛицо); АйдиСпец = ПолучитьАйдиКоннектТекПользователя(); Если ЗначениеЗаполнено(СервисИД) и ЗначениеЗаполнено(АйдиКлиента) и ЗначениеЗаполнено(АйдиСпец) Тогда ТекстСообщения = ""; ВвестиСтроку(ТекстСообщения, "Введите текст сообщения"); Если СтрДлина(ТекстСообщения) > 0 Тогда Результат = НаписатьСообщениеВКоннектЧерезПушАпи(СервисИД, АйдиКлиента, ТекстСообщения, АйдиСпец); Если НЕ Результат Тогда Ответ = ПодключитьЛиниюБоту(СервисИД); Если Ответ Тогда Результат = НаписатьСообщениеВКоннектЧерезПушАпи(СервисИД, АйдиКлиента, ТекстСообщения, АйдиСпец); КонецЕсли; КонецЕсли; Если Результат Тогда Сообщить("Сообщение успешно отправлено!"); Иначе Сообщить("Ошибка. Сообщение не отправлено!"); КонецЕсли; Иначе Сообщить("Текст сообщения не заполнен!"); КонецЕсли; Иначе Сообщить("У тек. пользователя нет доступных сервисов для связи с клиентом"); КонецЕсли; Если ИспользоватьБухФон() Тогда ДанныеСообщения = ПолучитьДанныеСообщения(АйдиКонтрагента, АйдиКлиента, СервисИД, АйдиСпец, ,ТекстСообщения); бфКлиент.ОбработатьСообщение(ДанныеСообщения); КонецЕсли; Возврат Результат; КонецФункции // НаписатьСообщениеВКоннект() Функция НаписатьСообщениеВКоннектЧерезПушАпи(АйдиЛинии, АйдиКлиента, ТекстСообщения, АйдиСпец = "", Соединение = Неопределено) Экспорт Если Соединение = Неопределено Тогда Соединение = ПолучитьСоединениеПушАпи(); КонецЕсли; ТекстЗапрос = "/v1/line/send/message/"; Заголовки = Новый Соответствие; Заголовки.Вставить("Content-Type", "application/json"); ЗаписьJSON = Новый ЗаписьJSON; ЗаписьJSON.УстановитьСтроку(); СтруктураЗапроса = Новый Структура; СтруктураЗапроса.Вставить("line_id", АйдиЛинии); СтруктураЗапроса.Вставить("text", ТекстСообщения); СтруктураЗапроса.Вставить("user_id", АйдиКлиента); Если ЗначениеЗаполнено(АйдиСпец) Тогда СтруктураЗапроса.Вставить("author_id", АйдиСпец); КонецЕсли; ЗаписатьJSON(ЗаписьJSON, СтруктураЗапроса); ТелоЗапроса = ЗаписьJSON.Закрыть(); Запрос = Новый HTTPЗапрос(ТекстЗапрос,Заголовки); Запрос.УстановитьТелоИзСтроки(ТелоЗапроса,КодировкаТекста.UTF8,ИспользованиеByteOrderMark.НеИспользовать); Попытка Результат = Соединение.ОтправитьДляОбработки(Запрос); Исключение #Если клиент Тогда Сообщить(ОписаниеОшибки()); #КонецЕсли КонецПопытки; Если Результат.КодСостояния <> 200 Тогда Сообщить("Код состояния " + Результат.КодСостояния); Возврат Ложь; КонецЕсли; Возврат Истина; КонецФункции
P.S. Первый раз пишу статью. Буду благодарен за адекватную критику и помощь в редактировании и улучшении статьи :)