Автоматическое уведомление клиентов по электронной почте в программе 1С: УВС


В данной статье предлагается внешняя обработка для конфигурации 1С: Управление ветеринарными сертификатами, выполняющая автоматическую рассылку писем по электронной почте клиентам компании, с целью уведомления о сформированных ветеринарно-сопроводительных документах.

В данной статье я хочу предложить полезную обработку для отраслевой конфигурации 1С: Управление Ветеринарными Сертификатами (УВС). Назначение предлагаемой обработки — автоматическая рассылка писем по электронной почте клиентам компании, с целью уведомления о сформированных ветеринарно-сопроводительных документов (ВСД).

Для кого будет полезна данная статья? Во-первых, для всех, кому приходится работать с программой 1С: УВС. Во-вторых, всем кому интересен вопрос формирования и отправки электронных писем средствами библиотеки БСП, с прикреплением к ним печатных форм. Также в статье рассмотрен вопрос реализации регламентного задания через внешнюю обработку.

Автоматизация уведомления клиентов — довольно распространенная задача, которую часто приходится решать, в том числе и 1С программистам, в контексте различных типовых и не очень конфигураций. Ниже представлен один из вариантов решения.

Представим себе ситуацию, когда некая компания, занимающаяся оптовой торговлей, поставляет своим клиентам продукты питания. Часть из них, как известно, подлежит ветеринарно-санитарному контролю (экспертизе). В соответствии с этим клиенты хотят получать ВСД, причем как можно раньше. Получать ВСД вместе с привезенной продукцией не всегда и не всех устраивает. В некоторых ситуациях чем раньше получены ВСД, тем клиенту лучше. Операторы при отгрузке продукции оформляют в программе УВС документы Транспортная операция, на основе которых рассматривается заявка о возможности и правомерности осуществления перевозки продукции. В случае одобрения заявки в программе автоматически формируются ВСД, доступные пользователю на закладке ВСД формы документа Транспортная операция. Для документа ВСД в конфигурации УВС реализовано несколько печатных форм. Наиболее подходящая нам — "Штрих-коды ВСД с дополнительной информацией" — она формирует т.н. форму 4 ветеринарной справки. Её и будем печатать и прикреплять в письму. Правда она содержит не штрих-код а QR-код — но мы на это не будем обращать внимание.

Сформулируем задачу в терминах понятных и близких 1С разработчикам: требуется реализовать регламентное задание на основе внешней обработки для автоматического уведомления клиента о сформированных ВСД, в виде электронного письма, с прикрепленными листами ВСД. Используем мы для всего этого конечно же БСП, которая хоть и частично, но все-таки встроена в конфигурацию УВС. 

Решение:

0) Настраиваем системную учетную запись, которую мы и мы будем использовать для отправки писем.

1) В справочнике ВидыКонтактнойИнформации добавляем новый элемент "EMail" в группу "Контактная информация справочника "Хозяйствующие субъекты"". В этом поле мы будем хранить адрес электронной почты контрагента. Заполняем для тех контрагентов, кому нужно отправлять уведомления.

2) Реализуем внешнюю обработку, в модуле объекта которой как обычно реализуем функцию СведенияОВнешнейОбработке:

Функция СведенияОВнешнейОбработке() Экспорт

ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("2.2.2.1");
ПараметрыРегистрации.Вид    = ДополнительныеОтчетыИОбработкиКлиентСервер.ВидОбработкиДополнительнаяОбработка();
ПараметрыРегистрации.Версия    = "1.0.0.1";
ПараметрыРегистрации.Наименование  = "ОтправкаУведомленияКонтрагентамПоЭлПочте";
ПараметрыРегистрации.БезопасныйРежим  = ЛОЖЬ;
ПараметрыРегистрации.Информация   = "Обработка отправляет контрагентам уведомление по эл. почте по факту успешно обработанной заявки (док. ТраснпортнаяОперация)";

НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
НоваяКоманда.Представление = НСтр("ru = 'Отправка уведомления контрагентам по эл. почте'");
НоваяКоманда.Идентификатор = "ОтправкаУведомленияКонтрагентамПоЭлПочте";
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовСерверногоМетода();

Возврат ПараметрыРегистрации;

КонецФункции

Также мы реализуем процедуру ВыполнитьКоманду, которая и будет вызываться по расписанию нашего регламентного задания:

Процедура ВыполнитьКоманду(ИдентификаторКоманды, ПараметрыВыполненияКоманды) Экспорт
Если ИдентификаторКоманды = "ОтправкаУведомленияКонтрагентамПоЭлПочте" Тогда
ВыполнитьОтправкуУведомлений();
КонецЕсли;
КонецПроцедуры

Процедура ВыполнитьОтправкуУведомлений будет реализована в этом же модуле, к её реализации мы вернемся несколько позже.

3) Размышляем на тему того, как мы будем запоминать для каких ВСД уведомления уже отправлены, а для каких надо отправлять. Потому что 2 раза одну и туже одобренную заявку отправлять не нужно. Если бы в УВС была реализована подсистема Свойства из БСП, то и вопросов бы не было — лично я добавил бы в документ ВСД дополнительный булевый реквизит УведомлениеОтправлено. Но так как в версии УВС 2.0.8.1 БСП внедрена не полностью и именно подсистемы Свойства в программе нет — придется выдумывать что-то другое. "Вскрывать" и дорабатывать типовую конфигурацию я не люблю, но в данном случае, возможно, это наиболее простой способ. Я предлагаю добавить в конфигурацию регистр сведений, который позволит нам "помнить" для каких ВСД мы уже отправили уведомление (не периодический, без регистратора).
 

4) Собственно сам алгоритм анализа ситуации с заявками и отправки уведомлений. Схема предельно простая: запросом получаем все необходимые данные, обрабатывая их формируем и отправляем письма.

Процедура ВыполнитьПоискНужныхДокументов() Экспорт

Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ РАЗЛИЧНЫЕ
| КонтрагентыКонтактнаяИнформация.Ссылка КАК Контрагент,
| КонтрагентыКонтактнаяИнформация.АдресЭП КАК АдресЭП
|ПОМЕСТИТЬ ВТ_Контрагенты
|ИЗ
| Справочник.Контрагенты.КонтактнаяИнформация КАК КонтрагентыКонтактнаяИнформация
|ГДЕ
| НЕ КонтрагентыКонтактнаяИнформация.Ссылка.ПометкаУдаления
| И КонтрагентыКонтактнаяИнформация.АдресЭП <> """"
| И КонтрагентыКонтактнаяИнформация.Тип = &ТипКИЭлПочта
| И КонтрагентыКонтактнаяИнформация.Вид = &ВидКИЭлПочта
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ТранспортныеОперации.Ссылка КАК Документ,
| ТранспортныеОперации.КонтрагентПолучатель КАК Контрагент,
| ТранспортныеОперации.ПредприятиеПолучатель КАК Предприятие,
| ТаблицаСтатусДокумента.Статус КАК Статус
|ПОМЕСТИТЬ ВТ_Документы
|ИЗ
| Документ.ТранспортныеОперации КАК ТранспортныеОперации
|  ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЖурналРегистрацииСостоянийЗаявокНаОформлениеОпераций.СрезПоследних(, ДокументСсылка ССЫЛКА Документ.ТранспортныеОперации) КАК ТаблицаСтатусДокумента
|  ПО (ТаблицаСтатусДокумента.ДокументСсылка = ТранспортныеОперации.Ссылка)
|ГДЕ
| ТранспортныеОперации.Дата >= &Дата
| И ТранспортныеОперации.КонтрагентПолучатель В
|   (ВЫБРАТЬ
|    ВТ_Контрагенты.Контрагент
|   ИЗ
|    ВТ_Контрагенты КАК ВТ_Контрагенты)
| И ТаблицаСтатусДокумента.Статус = &Статус
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Документы.Документ КАК Документ,
| ВТ_Документы.Контрагент КАК Контрагент,
| ВТ_Документы.Предприятие КАК Предприятие,
| ВТ_Документы.Статус КАК Статус,
| ВТ_Контрагенты.АдресЭП КАК АдресЭП
|ПОМЕСТИТЬ ВТ_ДокументыСАдресомЭлПочты
|ИЗ
| ВТ_Документы КАК ВТ_Документы
|  ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Контрагенты КАК ВТ_Контрагенты
|  ПО ВТ_Документы.Контрагент = ВТ_Контрагенты.Контрагент
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_ДокументыСАдресомЭлПочты.Документ КАК Документ,
| ВТ_ДокументыСАдресомЭлПочты.Контрагент КАК Контрагент,
| ВТ_ДокументыСАдресомЭлПочты.Предприятие КАК Предприятие,
| ВТ_ДокументыСАдресомЭлПочты.Статус КАК Статус,
| ВТ_ДокументыСАдресомЭлПочты.АдресЭП КАК АдресЭП
|ПОМЕСТИТЬ ВТ_НеотправленныеДанные
|ИЗ
| ВТ_ДокументыСАдресомЭлПочты КАК ВТ_ДокументыСАдресомЭлПочты
|  ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СтатусУведомленияКонтрагентаОЗаявке КАК СтатусУведомленияКонтрагентаОЗаявке
|  ПО ВТ_ДокументыСАдресомЭлПочты.Документ = СтатусУведомленияКонтрагентаОЗаявке.Документ
|   И ВТ_ДокументыСАдресомЭлПочты.Контрагент = СтатусУведомленияКонтрагентаОЗаявке.Контрагент
|ГДЕ
| СтатусУведомленияКонтрагентаОЗаявке.Дата ЕСТЬ NULL
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_НеотправленныеДанные.Документ КАК Документ,
| ВТ_НеотправленныеДанные.Документ.Номер КАК НомерДокумента,
| ВТ_НеотправленныеДанные.Документ.Дата КАК ДатаДокумента,
| ВТ_НеотправленныеДанные.Документ.НомерТТН КАК НомерТТН,
| ВТ_НеотправленныеДанные.Документ.ДатаТТН КАК ДатаТТН,
| ВТ_НеотправленныеДанные.Документ.СерияТТН КАК СерияТТН,
| ВТ_НеотправленныеДанные.Контрагент КАК Контрагент,
| ВТ_НеотправленныеДанные.Контрагент.Наименование КАК НаименованиеКонтрагента,
| ВТ_НеотправленныеДанные.Документ.КонтрагентОтправитель.Наименование КАК НаименованиеКонтрагентаОтправителя,
| ВТ_НеотправленныеДанные.Предприятие КАК Предприятие,
| ВТ_НеотправленныеДанные.Статус КАК Статус,
| ВТ_НеотправленныеДанные.АдресЭП КАК АдресЭП
|ИЗ
| ВТ_НеотправленныеДанные КАК ВТ_НеотправленныеДанные
|ИТОГИ ПО
| Контрагент,
| АдресЭП
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ТранспортныеОперацииТаблицаВСД.ВСД КАК ВСД,
| ТранспортныеОперацииТаблицаВСД.ВСД.Номер КАК НомерВСД,
| ТранспортныеОперацииТаблицаВСД.ВСД.Дата КАК ДатаВСД,
| ТранспортныеОперацииТаблицаВСД.Ссылка КАК Документ
|ИЗ
| Документ.ТранспортныеОперации.ТаблицаВСД КАК ТранспортныеОперацииТаблицаВСД
|ГДЕ
| ТранспортныеОперацииТаблицаВСД.Ссылка В
|   (ВЫБРАТЬ
|    ВТ_НеотправленныеДанные.Документ КАК Документ
|   ИЗ
|    ВТ_НеотправленныеДанные КАК ВТ_НеотправленныеДанные)
|
|УПОРЯДОЧИТЬ ПО
| Документ,
| ВСД";

Запрос.УстановитьПараметр("Дата",    НачалоДня(ТекущаяДата()));
Запрос.УстановитьПараметр("Статус",   Справочники.СтатусыЗаявок.УспешноОбработана);
Запрос.УстановитьПараметр("ВидКИЭлПочта",  Справочники.ВидыКонтактнойИнформации.НайтиПоНаименованию("EMail"));
Запрос.УстановитьПараметр("ТипКИЭлПочта",  Перечисления.ТипыКонтактнойИнформации.АдресЭлектроннойПочты);

РезПакет = Запрос.ВыполнитьПакет();

времУчЗаписьЭлПочты = Справочники.УчетныеЗаписиЭлектроннойПочты.СистемнаяУчетнаяЗаписьЭлектроннойПочты;
времТекДата   = ТекущаяДата();

ВыборкаКонтрагентов = РезПакет[4].Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
ТЗ_ВСД = РезПакет[5].Выгрузить();

Пока ВыборкаКонтрагентов.Следующий() Цикл

ВыборкаЭлПочтаКонтрагента = ВыборкаКонтрагентов.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаЭлПочтаКонтрагента.Следующий() Цикл

ВыборкаДокументы = ВыборкаЭлПочтаКонтрагента.Выбрать();
Пока ВыборкаДокументы.Следующий() Цикл

ОтборВСД = Новый Структура("Документ", ВыборкаДокументы.Документ);
времМассивВСД = ТЗ_ВСД.НайтиСтроки(ОтборВСД);

Если времМассивВСД.Количество() > 0 Тогда

НачатьТранзакцию();
Попытка
времОписание = "Подготовка отправки уведомления контрагенту "
+ СТРОКА(ВыборкаДокументы.НаименованиеКонтрагента) + " по ТТН " + СТРОКА(ВыборкаДокументы.НомерТТН)
+ " от " + СТРОКА(ВыборкаДокументы.ДатаТТН) + ", серия " + СТРОКА(ВыборкаДокументы.СерияТТН);

ЗаписьЖурналаРегистрации(
"ОтправкаУведомленияОбУспешнойОбработкиЗаявки.Подготовка",
УровеньЖурналаРегистрации.Информация,
,
,
времОписание);

времИдентификаторПочтовогоСообщения = "";
Если ОтправитьЭлектронноеПисьмоУведомления(времУчЗаписьЭлПочты, ВыборкаДокументы, времИдентификаторПочтовогоСообщения, времМассивВСД) Тогда
ЗаписатьФактОтправкиУведомленияВРегСведений(
времТекДата,
ВыборкаДокументы.Контрагент,
ВыборкаДокументы.Документ,
"Данные о заявке успешно переданы",
ВыборкаДокументы.АдресЭП);
КонецЕсли;

Исключение
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
ВызватьИсключение;
КонецПопытки;

Если ТранзакцияАктивна() Тогда
ЗафиксироватьТранзакцию();
КонецЕсли;

КонецЕсли;
//Если времМассивВСД.Количество() > 0 Тогда

КонецЦикла;

КонецЦикла;

КонецЦикла;

КонецПроцедуры

Код и алгоритм запроса в комментариях не нуждается. Для удобства отладки механизма я использую запись в журнал регистрации. А вот процедура ОтправитьЭлектронноеПисьмоУведомления более интересная, хотя бы потому что именно в ней происходит формирование электронных писем.

Функция ОтправитьЭлектронноеПисьмоУведомления(УчЗаписьЭлПочты, ДанныеОЗаявке, ИдентификаторПочтовогоСообщения, МассивВСД)

УведомлениеОтправлено = ЛОЖЬ;

// Массив временных файлов, который нужно будет обязательно
// удалить в конце работы данного алгоритма
МассивУдаляемыхФайлов = Новый Массив;

Попытка

массивВложений = Новый Массив;
//   * Вложения - Массив - файлы, которые необходимо приложить к письму (описания в виде структур):
//     ** Представление - Строка - имя файла вложения;
//     ** АдресВоВременномХранилище - Строка - адрес двоичных данных вложения во временном хранилище.
//     ** Кодировка - Строка - кодировка вложения (используется, если отличается от кодировки письма).
//     ** Идентификатор - Строка - (необязательный) используется для отметки картинок, отображаемых в теле письма.

темаПисьма = "Успешно обработана заявка № " + СТРОКА(ДанныеОЗаявке.НомерДокумента) + " от " + СТРОКА(ДанныеОЗаявке.ДатаДокумента);

телоПисьма = темаПисьма + "." + СИМВОЛЫ.ПС;

// Поместим прикрепленные к транспортной операции документы ВСД в массив
массивДокументовВСД = Новый Массив;

// Имеет смысл отправлять письмо, только если для данной транспортной
// операции найдены ВСД
Если МассивВСД.Количество() > 0 Тогда
телоПисьма = телоПисьма + СИМВОЛЫ.ПС + СИМВОЛЫ.ПС + "Список ВСД:";

Для Каждого стрВСД ИЗ МассивВСД Цикл
массивДокументовВСД.Добавить(стрВСД.ВСД);
телоПисьма = телоПисьма + СИМВОЛЫ.ПС + "    " + СТРОКА(стрВСД.НомерВСД) + " от " + СТРОКА(стрВСД.ДатаВСД);
КонецЦикла;

// Сформируем табличный документ для всех ВСД привязаных к текущей транспортной операции
ТабДокВСД = Документы.ВСД.СформироватьПечатнуюФормуСжатогоВСДСИнформацией(массивДокументовВСД, Неопределено);
// Получаем путь временного файла на диске
ВремФайлНаДиске = ПолучитьИмяВременногоФайла("pdf");
// Сохраним табличный документ во временный файл
ТабДокВСД.Записать(ВремФайлНаДиске, ТипФайлаТабличногоДокумента.PDF);
// Добавляем временный файл в массив удаляемых файлов
МассивУдаляемыхФайлов.Добавить(ВремФайлНаДиске);

// Помещаем сохраненный временный файл во временное хранилище
ДвоичныеДанные = Новый ДвоичныеДанные(ВремФайлНаДиске);
Идентификатор  = Новый УникальныйИдентификатор;
АдресВоВременномХранилище = ПоместитьВоВременноеХранилище(ДвоичныеДанные, Идентификатор);

// Формируем вложение для нашего письма
времВложение = Новый Структура("Представление,АдресВоВременномХранилище,Кодировка,Идентификатор");
времВложение.Представление    = "ВСД " + Строка(ДанныеОЗаявке.НомерДокумента) + ".pdf";
времВложение.АдресВоВременномХранилище = АдресВоВременномХранилище;
//времВложение.Кодировка
//времВложение.Идентификатор

массивВложений.Добавить(времВложение);

КонецЕсли;

телоПисьма = телоПисьма + СИМВОЛЫ.ПС + СИМВОЛЫ.ПС
+ "С уважением," + СИМВОЛЫ.ПС
+ "  Некто";

СтруктураПолучатель = Новый Структура("Адрес,Представление");
СтруктураПолучатель.Адрес   = ДанныеОЗаявке.АдресЭП;
СтруктураПолучатель.Представление = ДанныеОЗаявке.НаименованиеКонтрагента;

времПолучателиСообщения = Новый Массив;
времПолучателиСообщения.Добавить(СтруктураПолучатель);

СтруктураОтправитель = Новый Структура("Адрес,Представление");
СтруктураОтправитель.Адрес   = УчЗаписьЭлПочты.АдресЭлектроннойПочты;
СтруктураОтправитель.Представление = СТРОКА(УчЗаписьЭлПочты); // "Системная учетная запись";
времАдресаОтвета = Новый Массив;
времАдресаОтвета.Добавить(СтруктураОтправитель);

времПараметрыПисьма = Новый Структура();
времПараметрыПисьма.Вставить("Кому",       ДанныеОЗаявке.АдресЭП);
времПараметрыПисьма.Вставить("ПолучателиСообщения",   времПолучателиСообщения);
//времПараметрыПисьма.Вставить("Копии",      времКопии);
времПараметрыПисьма.Вставить("Тема",      темаПисьма);
времПараметрыПисьма.Вставить("Тело",      телоПисьма);
времПараметрыПисьма.Вставить("Важность",     ВажностьИнтернетПочтовогоСообщения.Обычная);
времПараметрыПисьма.Вставить("АдресОтвета",     времАдресаОтвета);
времПараметрыПисьма.Вставить("УведомитьОДоставке",   ЛОЖЬ); // ИСТИНА);
времПараметрыПисьма.Вставить("УведомитьОПрочтении",   ЛОЖЬ); // ИСТИНА);
времПараметрыПисьма.Вставить("ТипТекста",     Перечисления.ТипыТекстовЭлектронныхПисем.ПростойТекст);

// Вложения
Если массивВложений.Количество() > 0 Тогда
времПараметрыПисьма.Вставить("Вложения", массивВложений);
КонецЕсли;

ИдентификаторПочтовогоСообщения = РаботаСПочтовымиСообщениями.ОтправитьПочтовоеСообщение(УчЗаписьЭлПочты, времПараметрыПисьма);

ЗаписьЖурналаРегистрации(
"ОтправкаУведомленияОбУспешнойОбработкиЗаявки.ОтправкаЭлПисьма",
УровеньЖурналаРегистрации.Информация,
,
,
"Электронное письмо успешно отправлено");

УведомлениеОтправлено = ИСТИНА;

Исключение

ЗаписьЖурналаРегистрации(
"ОтправкаУведомленияОбУспешнойОбработкиЗаявки.ОтправкаЭлПисьма",
УровеньЖурналаРегистрации.Ошибка,
,
,
"Ошибка при отправке электронного письма: " + СТРОКА(ОписаниеОшибки()));

КонецПопытки;

// Удаляем временные файлы
Для Каждого фл ИЗ МассивУдаляемыхФайлов Цикл
УдалитьФайлы(фл);
КонецЦикла;

Возврат УведомлениеОтправлено;

КонецФункции


// Запись в регистр сведений СтатусУведомленияКонтрагентаОЗаявке факта отправки
// уведомления об обработанной заявке
Процедура ЗаписатьФактОтправкиУведомленияВРегСведений(Дата, Контрагент, Документ, Описание, АдресЭлПочты)

НаборЗаписей = РегистрыСведений.СтатусУведомленияКонтрагентаОЗаявке.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Документ.Установить(Документ);
НаборЗаписей.Отбор.Контрагент.Установить(Контрагент);
НаборЗаписей.Прочитать();
НаборЗаписей.Записать();

новЗап = НаборЗаписей.Добавить();
новЗап.Документ   = Документ;
новЗап.Контрагент  = Контрагент;
новЗап.Дата    = Дата;
новЗап.Описание   = Описание;
новЗап.АдресЭлПочты  = АдресЭлПочты;

НаборЗаписей.Записать();

ЗаписьЖурналаРегистрации(
"ОтправкаУведомленияОбУспешнойОбработкиЗаявки.ЗаписьВРегистрСтатусовУведомлений",
УровеньЖурналаРегистрации.Ошибка,
,
,
"Добавлена запись в регистр СтатусУведомленияКонтрагентаОЗаявке");

КонецПроцедуры

Как видно код данной процедуры "заправлен" достаточным количество комментариев. При использовании процедуры ОтправитьПочтовоеСообщение модуля РаботаСПочтовымиСообщениями советую тщательно изучить описание её параметров.

Процедура ЗаписатьФактОтправкиУведомленияВРегСведений создает запись в регистре сведений СтатусУведомленияКонтрагентаОЗаявке. Приводить её код я не стану, потому что это это распространенная типовая задача.

Далее требуется добавить нашу ВО в программу, указав для неё возможность запуска по расписанию и определив само расписание. После этого, как говорится, все должно работать. Буду рад, если кому то мое творчество принесет пользу. 

Все это было протестировано и успешно работает на версии УВС 2.0.8.1 в клиент-серверном варианте работы программы. Благодарю за внимание! Буду благодарен за ваши комментарии, отзывы и советы.

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *