В данной статье я хочу предложить полезную обработку для отраслевой конфигурации 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 в клиент-серверном варианте работы программы. Благодарю за внимание! Буду благодарен за ваши комментарии, отзывы и советы.