Skip to content

Instantly share code, notes, and snippets.

  • Save mdemyanov/bc884c43dad1c14620586c4b490eda41 to your computer and use it in GitHub Desktop.
Save mdemyanov/bc884c43dad1c14620586c4b490eda41 to your computer and use it in GitHub Desktop.
Закрытие заявок из почты без перехода по ссылкам

Как закрыть заявку из email не переходя в интерфейс ITSM 365

Зачем это нужно

Такая опция будет полезна в случаях если:

  1. Вы не предоставляете своим клиентам/пользователям личный кабинет и общаетесь только в почте
  2. Ваши клиенты/пользователи предпочитают работать через почту
  3. Вы не хотите использовать email с ключами доступа, так как их случайная пересылка может скомпрометировать учетные записи

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

Как это работает

Суть этого способа заключается в том, что вместо ссылок на WEB интерфейс системы мы будем использовать ссылку mailto, которая содержит шаблон нового письма:

  1. Пользователь получает письмо о решении заявки с возможностью указать оценку
  2. Он кликает по url с нужной оценкой, и попадает на форму нового письма
  3. Новое письмо уже содержит тему и адрес получателя
  4. При этом в теме в установленном формате хранится идентификатор заявки, а также перечень действий (например, смена статуса и проставление оценки)
  5. Пользователь отправляет письмо
  6. Специальный обработчик (файл с ним вы найдете ниже), обрабатывает такие письма, проставляя оценки по заявкам или другие действия, которые вы поместили в url

Что нужно настроить

Создать почтовый ящик для обработки таких писем

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

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

Добавить новое правило обработки входящей почты

  1. Добавьте новое правило обработки входящей почты: https://www.naumen.ru/docs/sd/NSD_manual.htm#mail/incomingMail_rules.htm
  2. Заполните скрипт в зависимости от назначения настройки
  • Если автоматизация нужна для контрагента заявки - спользуйте скрипт "[Правило обработки писем] Обработка действий пользователей (контрагентов).groovy"
  • Если автоматизация нужна для специалистов - спользуйте скрипт "[Правило обработки писем] Обработка действий специалистов.groovy"

Настроить новое подключение к почте и правила ее обработки

  1. Настройте новое почтовое подключение: https://www.naumen.ru/docs/sd/NSD_manual.htm#mail/incomingMail_server_add.htm
  2. Добавьте новое правило обработки писем (из файла ниже): https://www.naumen.ru/docs/sd/NSD_manual.htm#mail/incomingMail_rules.htm
  3. Настройте задачу планировщика, которая будет обрабатывать эти письма: https://www.naumen.ru/docs/sd/NSD_manual.htm#mail/incomingMail_planer.htm Если в процессе настроек у вас возникнут вопросы, вы всегда можете обратиться в службу поддержки ITSM 365

Добавить или изменить ссылки в оповещениях о решении заявки

Как мы уже писали выше, новые ссылки будут использовать протокол mailto. Кликая по такой ссылке пользователь попадет на форму нового письма в своем почтовом клиенте. Каждая такая ссылка состоит из следующих блоков:

  1. mailto:someone@example.com - основная часть url с методом и адресом ящика, куда слоть письма
  2. ?subject=url_encoded_string - ?subject - указывает на то, что есть значение для темы, а после знака "=" идет строка с параметрами. Именно в этом месте нужно указать идентификатор заявки и что с ней нужно делать

В качестве идентификатора заявки мы используем ее UUID. После него идет перечень атрибутов заявки и значения, которые необходимо изменить. Примеры:

  1. Закрыть заявку с оценкой 5: serviceCall$00000,state:closed,mark:5
  2. Закрыть заявку с оценкой 4: serviceCall$00000,state:closed,mark:4
  3. Возобновить заявку: serviceCall$00000,state:resumed

Сами ссылки при этом будут выглядеть следующим образом:

<a href="mailto:someone@example.com?subject=serviceCall$00000,state:closed,mark:5">Оценить на 5</a>
<a href="mailto:someone@example.com?subject=serviceCall$00000,state:closed,mark:4">Оценить на 4</a>
<a href="mailto:someone@example.com?subject=serviceCall$00000,state:resumed">Возобновить заявку 💔</a>

Все остальные параметры URL, например, заполнение тела письма и прочее, опционально, можете использовать на свое усмотрение.

Подробную информацию по настройке оповещений вы можете получить в руководстве технолога: https://www.naumen.ru/docs/sd/NSD_manual.htm#setting_sistem/action_notification.htm

/*! UTF8 */
//Автор: mdemyanov
//Дата создания: 24.04.2020
//Код:
//Назначение:
/**
* Обработка действий из писем
* Обработчик парсит тему письма по приципу:
* - все аргументы разлелены запятыми
* - первый аргумент содержит UUID объекта
* - остальные аргументы представляют собой справочник ключ/значение
*/
//Версия: 4.8.*
//Категория:
//Параметры------------------------------------------------------
class Constants {
static final SUBJECT_NOT_FOUND = 'Не удалось получить объект.'
static final DIFF_EMPLOYEE = 'Нельзя закрывать чужую заявку.'
static final CONTEXT_SPLITTER = /,/
static final ATTRS_SPLITTER = /:/
static final ATTRS_COLLECTOR = { String data ->
def (key, value) = data.split(ATTRS_SPLITTER)
return [(key): value]
}
class ServiceCall {
static final UUID = 'UUID'
static final CLIENT_EMAIL = 'clientEmail'
static final CLIENT_EMPLOYEE = 'clientEmployee'
}
class Employee {
static final FQN = 'employee'
static final EMAIL = 'email'
}
}
//Функции--------------------------------------------------------
//Основной блок -------------------------------------------------
List<String> subjectData = message.subject.split(Constants.CONTEXT_SPLITTER)
def subject = utils.get(subjectData.remove(0))
if (subject == null) {
result.reject(api.mail.OTHER_REJECT_REASON, Constants.SUBJECT_NOT_FOUND)
return 0
}
def clientEmployee = subject[Constants.ServiceCall.CLIENT_EMPLOYEE]
def subjectClientEmail = clientEmployee?."${Constants.Employee.EMAIL}" ?: subject[Constants.ServiceCall.CLIENT_EMAIL]
if (subjectClientEmail != message.from.address) {
result.reject(api.mail.CONTACT_NOT_FOUND_REJECT_REASON, Constants.DIFF_EMPLOYEE)
return 0
}
def comment = message.bodyRTF
def employee = utils.get(Constants.Employee.FQN, [(Constants.Employee.EMAIL): message.from.address])
def attrs = subjectData.collectEntries(Constants.ATTRS_COLLECTOR)
if (comment) {
attrs['@comment'] = comment
}
if (employee) {
attrs['@commentAuthor'] = employee
}
try {
api.tx.call{
utils.edit(subject, attrs)
}
} catch (Exception e) {
result.error(e.message)
}
/*! UTF8 */
//Автор: mdemyanov
//Дата создания: 25.05.2020
//Код: manageServiceCallsOtherEmail
//Назначение:
/**
* Сценарий обработки почты для управления заявкой через тему письма
* Обработчик парсит тему письма по приципу:
* * - все аргументы разлелены запятыми
* * - первый аргумент содержит number объекта
* * - остальные аргументы представляют собой справочник ключ/значение
* Условие возможности модификации заявок:
* - у заявке должен быть установлен ответственный
* - домен email ответственного должен совпадать с email отправителя письма
* В случае ошибки или невозможности модификации заявки отправитель получит соответствующее уведомление на email.
*/
//Версия: 4.8.*
//Категория:
//Параметры------------------------------------------------------
class Constants {
static final CONTEXT_SPLITTER = /,/
static final ATTRS_SPLITTER = /:/
static final ATTRS_COLLECTOR = { String data ->
def (key, value) = data.split(ATTRS_SPLITTER)
return [(key): value]
}
}
//Функции--------------------------------------------------------
//Основной блок -------------------------------------------------
def fromAddress = message.from.address
def fromName = message.from.name
def fromDomain = message.from.domain
List<String> subjectData = message.subject.split(Constants.CONTEXT_SPLITTER)
def subject = utils.get('serviceCall', [number: subjectData.remove(0), removed: false])
if (subject == null) {
result.reject(api.mail.OTHER_REJECT_REASON, 'Не удалось получить объект.')
api.mail.sender.send(fromAddress, fromName,
'Re: ' + message.subject,
'Не удалось найти заявку по номеру.\n' +
'Пожалуйста, проверьте правильность введенного вами номера ' +
'или напишите владельцу системы, возможно, эта заявка была удалена.')
return 0
}
def responsibleEmployee = subject.responsibleEmployee
if (responsibleEmployee == null) {
result.reject(api.mail.OTHER_REJECT_REASON, 'В заявке нет ответственного')
api.mail.sender.send(fromAddress, fromName,
'Re: ' + message.subject,
'Эта заявка находится вне вашей зоны ответственности.\n' +
'Пожалуйста, проверьте правильность введенного вами номера ' +
'или напишите владельцу системы.0')
return 0
}
def responsibleEmailDomain = responsibleEmployee.email.split('@').last()
if (responsibleEmailDomain != fromDomain) {
result.reject(api.mail.OTHER_REJECT_REASON, 'Заявка вне зоны ответственности отправителя')
api.mail.sender.send(fromAddress, fromName,
'Re: ' + message.subject,
'Эта заявка находится вне вашей зоны ответственности.\n' +
'Пожалуйста, проверьте правильность введенного вами номера ' +
'или напишите владельцу системы.')
return 0
}
def comment = message.bodyRTF
def attrs = subjectData.collectEntries(Constants.ATTRS_COLLECTOR)
if (comment) {
attrs['@comment'] = comment
attrs['@commentAuthor'] = responsibleEmployee
}
try {
api.tx.call {
utils.edit(subject, attrs)
utils.event(subject, "Внесены изменения через обработчик почты от $fromAddress.")
}
} catch (Exception e) {
result.error(e.message)
api.mail.sender.send(fromAddress, fromName,
'Re: ' + message.subject,
'Во время обработки вашего письма возникла непредвиденная ошибка.\n' +
'Пожалуйста, напишите владельцу системы.')
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment