Языки программирования - Отправка печатных форм 1С как рисунка (tif, gif, etc.) по E-mail - PRCY⮭net
Постановка задачи, история


Все началось до банального просто - любимый директор сказал "Хочу!". Аргументация была следующей:

* Переводится много бумаги для печати и отправки по факсу (клиентов много, потому отправленные счета сразу выбрасываются: найти нужный документ даже через день - нереально)
* Электронная почта "есть в наши дни у всех и каждого" (то, что сам директор ею не пользуется - другой вопрос :-) )
* Тратится меньше времени персонала (не нужно сидеть и ждать перед факсом, стартовать, "прошло"/"не прошло", ...)
* Легче вести учет когда и что было отправлено.


Сначала ставился вопрос отправки документов вообще - что может быть проще? Сохранить таблицу как файл MS-Excel, вызвать внешнюю программу отправки с параметрами - и все. Потом возникли сомнения:

* А вот клиенты отредактируют файл - и будут доказывать что мы такой и отправили,
* В файле передается рисунок печати - они его смогут использовать с какой-нибудь темной целью.


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


Подбор нужного инструментария


Некоторое время я стараюсь использовать бесплатные программы, а не ломать те, за которые нужно платить деньги. Так что одним из условий (не главным, но в результате выполненным почти на 100%) была бесплатность инструментария.
Понятно, что для получения рисунка на выходе нужен виртуальный принтер, на который можно печатать любой документ. Выходным форматом был выбран tiff как достаточно распространенный, предполагая что его можно будет конвертировать в любой формат, если возникнет необходимость. Были испробованы многие принтеры, встреченные в просторах Internet`а, как бесплатные, так и нет. Большинство из них умеют печатать кроме искомого tiff еще и pdf документы, но не один не удовлетворял условиям передачи в них внешних параметров (важно было указать место сохранения и возможно имя файла для уменьшения коллизий, поскольку работа происходит на сервере терминалов). В конечном итоге выбор пал на AFPL Ghostscript 8.14 for Win32 и драйвер переадресации порта принтера RedMon.
Ghost Script умеет конвертировать данные из ps, eps, pdf в разные форматы (те же ps, eps, pdf, языки принтеров вроде PCL6 от HP, и рисунки). Получать данные он может как из файла, так и из входящего потока (stdin для посвященных). RedMon умеет данные, полученные от драйвера принтера, передавать как входной поток выбранной программе. Кроме того устанавливает несколько системных переменных, одну из которых (%REDMON_USER% - имя пользователя, печатающего документ) мы будем использовать.
Итак - используемый режим связки: установка PS принтера в системе, указание ему виртуального порта RedMon, пересылка исходящего PS потока от принтера на Ghost Script, формирование tif по указанным настройкам.
Настройки для режима работы Ghost Script хранятся в файле одном для всех, потому в схему добавим еще одно звено: RedMon передает данные не Ghost Script, а скрипту WSH, а уже он откорректировав настройки под пользователя, передает дальше поток для Ghost Script. Потому еще одна программа, которая нам нужна: Windows Script 5.6 for Windows. Нужна именно версия 5.6, поскольку во встроенной в Windows 2000 версии 5.1 отсутствует необходимый метод Exec().
Еще возможно нам понадобится компонент для вывода рисунков с прозрачным фоном. Пока приходится использовать Active_BMP, упоминаемый на безвременно почившем hare.ru. Этот компонент умеет отображать прозрачными только 2-х цветные bmp (по крайней мере только с ними у меня получилось добиться прозрачности), но за неимением лучшего... :-) (Если кто знает бесплатный ActiveX компонент для отображения gif с прозрачным слоем - скажите в форум или мыло)
Собственно для отправки почты из командной строки я уже полгода пользуюсь Postie, потому искать ничего нового не пришлось.


Приступим (установка и регистрация программ)


Установка WSH проблем не вызывает (конечно, если вы не попытаетесь установить версию для 9X/NT4 на 2000/XP, как я это сделал, причем осознал это только взявшись за статью - уже месяц сервер живет в этом режиме :-) ): запуск scripten.exe (scr56en.exe), ответы на все вопросы, перезагрузка.
Установка Ghost Script не требует даже перезагрузки. Единственный момент - от пытается по умолчанию установится в каталог %SystemDrive%\gs - я его устанавливал в %SystemDrive%\Tools\gs - так мне удобнее. (ниже в скобках я буду писать свои настройки, с которыми у меня работает живая система).
Для установки RedMon нужно его распаковать в некий каталог (%SystemDrive%\Tools\RedMon) и запустить setup.exe из него. В файлах readme.txt и redmon.hlp находится подробная информация по установке и стандартной настройке redmon.
Регистрация Active_BMP осуществляется распаковкой файлов в каталог (%SystemDrive%\Tools\OLE\ActiveBMP) и запуском из этого каталога "regsvr32 Bmp_1c.ocx".
В дальнейшем каталоги с RedMon и Active_BMP нам не понадобятся, так что про них смело можно забыть (но не удалять совсем с диска :-) ).
Postie устанавливается простым извлечение его в нужный каталог (%SystemDrive%\Tools\Postie).
Теперь нам необходимо настроить принтер. Для этого из папки принтеры выбираем "Добавить". Тип принтера - локальный, отказываемся от автоматического поиска и добавляем порт: тип порта: Redirect Port, имя: RPT1. На следующем шаге выбираем модель PS-принтера (в RedMon рекомендуется Apple LaserWriter II NT или Apple Color LaserWriter 12/600 если вы хотите цветное изображение). Я использовал Apple LaserWriter II NT, т.к. мне нужно было черно-белое изображение. Сразу после этого я переименовал принтер в более соответствующее его функциям название: "Send EMail". Теперь нам необходимо настроить порт. Для этого открываем настройки принтера, ищем страницу "Порты" и жмем кнопку "Конфигурировать порт".
Дальнейшие настройки отличаются от стандартных, описанных в redmon.hlp:

* "Redirect this port to the program:"="cscript.exe" (без кавычек, естественно),
* "Arguments for this programs are:"="Наш\Скрипт\С\Полным\Путем.js" (%SystemDrive%\Tools\gs\PrnUser.js) (в кавычках, если путь содержит пробелы),
* "Output:"="Program handles output"
* "Run:"="Hidden"
* "Run as user" снята (у меня вызывало ошибку, если установлено)
* "Shut down delay:"="300"


Кнопка "Log file" нужна во время отладки всей системы отправки почты, хотя можно оставить запись лога и в рабочем режиме - все равно он перезаписывается, а не накапливается.


Соглашения о настройках


Скрипт, который мы указали в настройках порта, принимает данные с принтера и согласно настройкам, сохраненным из внешней программы (1С или другой), отправляет его по почте как рисунок (в скрипте предусмотрены проверки на корректность значений). Поскольку единственное, что мы можем получить из печатного задания - это имя пользователя (%REDMON_USER%), то с каждым пользователем мы будем работать в его каталоге, при этом одновременная печать 2-х заданий от одного пользователя невозможна. (Если вам удастся передать в скрипт другую информацию из 1С, например: уникальный идентификатор задания или имя файла - сообщите мне). У меня используется самописный компонент SysTools для получения профиля пользователя по его имени. Поскольку он еще только в альфа-версии выкладывать не буду, если кому нужен - вышлю по почте. Итак, предположим, у нас есть каталог, в котором хранятся данные пользователей (%MyProfiles%\User1, %MyProfiles%\User2, ...). К личном каталоге пользователя мы будем создавать подкаталог SendMail для отправки почты.
Временные файлы для работы мы будем хранить во временном каталоге (переменная %TEMP% для системы, поскольку запускаться скрипт будет от имени Local service).
Все остальные настройки и пути к файлам заданы в переменных вначале скрипта - их можно (и нужно) изменить для себя.
Файл, в котором 1С сохраняет настройки называется %UserProfile%\SendMail\mail.ini и имеет следующую структуру: каждая строка - поле=значение, кроме поля BODY, которое обязательно идет последним и может быть растянуто на несколько строк.


Пишем программу


В этом разделе будут показаны и пояснены тексты нескольких модулей, входящих в демонстрационную конфигурацию. Скрипт на языке JavaScript здесь описан не будет, поскольку несоответствует тематике раздела. Надеюсь - комментариев внутри скрипта будет достаточно для пожелавших разобраться в его работе.
Поскольку в 1С не предусмотрена модульная организация программ, то сложные вещи я обычно строю по такой схеме: законченная функциональность - во внешней обработке, параметры в которую передаются через СписокЗначений, и вспомагательная процедура/функция в глобальном модуле, которая этот список заполняет из параметров. Так было сделано и здесь.
Функция запроса параметров отправки почты (кому, от кого, тема и пр.) в глобальном модуле выглядит так:

Код: (1c)
// ===============================
// Предлагает изменить параметры отправки почты
// Возвращает имя файла настроек или пустую строку при неудаче
Функция глПараметрыОтправкиПочты(Заголовок,Кому,ОтКого,Копия="",
Тема="",Сообщение="",Запретить="", БезФормы=0) Экспорт
Перем Сп, Каталог, ИмяФайла, Т;

// Устанавливаем параметры обработки
Сп=СоздатьОбъект("СписокЗначений");
Сп.Установить("Заголовок",Заголовок);
Сп.Установить("Кому",Кому);
Сп.Установить("ОтКого",ОтКого);
//Сп.Установить("ОтКогоФирма","[email protected]"); - адрес по умолчанию, если ОтКого не задан
Сп.Установить("Копия",Копия);
Сп.Установить("Тема",Тема);
Сп.Установить("Сообщение",Сообщение);
Сп.Установить("Запретить",Запретить);
Сп.Установить("ТихийРежим",БезФормы);
// Вызываем обработку подтверждения параметров
ОткрытьФормуМодально("Отчет", Сп, КаталогИБ()+"ExtForms\ПараметрыОтправкиПочты.ert");

// Если нажата "Отмена" - выходим
Если ПустоеЗначение(Сп)=1 Тогда Возврат ""; КонецЕсли;

// Считываем возвращенные значения
ОтКого=Строка(Сп.Получить("ОтКого"));
Кому=Строка(Сп.Получить("Кому"));
Копия=Строка(Сп.Получить("Копия"));
Тема=Строка(Сп.Получить("Тема"));
Сообщение=Строка(Сп.Получить("Сообщение"));
Попытка
Каталог=КаталогСистемногоПользователя();
Если ПустаяСтрока(Каталог)=1 Тогда Возврат ""; КонецЕсли; // Неизвестный пользователь

Каталог=Каталог+КаталогПочты;

// Ожидаем завершения предыдущей задачи
Ответ="Повтор";
Пока (ФС.СуществуетФайл(Каталог+ФайлФлага)=1) ИЛИ
(ФС.СуществуетФайл(Каталог+ФайлНастроек)=1) Цикл
ТекстВопроса=ТекущееВремя()+": Ожидается завершение задачи."+
РазделительСтрок+"Автоматический повтор через 5 секунд...";
Ответ=Вопрос(ТекстВопроса, "Повтор+Отмена", 5);
// Возможность прервать ожидание
Если Ответ="Отмена" Тогда Прервать; КонецЕсли;
КонецЦикла;
Если Ответ="Отмена" Тогда
ИмяФайла="";
Иначе
// Записываем параметры в файл
ИмяФайла=Каталог+ФайлНастроек;
Т=СоздатьОбъект("Текст");
Т.ДобавитьСтроку("TO="+ Кому);
Т.ДобавитьСтроку("FROM="+ ОтКого);
Т.ДобавитьСтроку("CC="+ Копия);
Т.ДобавитьСтроку("BCC="+ ОтКого);
Т.ДобавитьСтроку("SUBJ="+ Тема);
Т.ДобавитьСтроку("ORG="+ "Тестовая конфигурация Send E-Mail");
Т.ДобавитьСтроку("BODY="+ Сообщение);
Т.Записать(ИмяФайла);
КонецЕсли;
Исключение
Сообщить(ОписаниеОшибки(),"!!!");
Возврат "";
КонецПопытки;

Возврат ИмяФайла;
КонецФункции

В этой функции переданные параметры записываются в список значений, который передается внешней обработке ПараметрыОтправкиПочты.ert в подкаталоге ExtForms каталога базы данных. Запрос параметров имеет вид:


Возвращенные значения записываются в файл, параметры которого (путь, имя, и т.п.) заданы в конце глобального модуля.
В самой обработке ничего интересного нет: чтение параметров из списка, отображение и проверка параметров при нажатии кнопки Отправить. Если не заданы необходимые параметры (ОтКого, Кому) или адреса E-Mail указаны не правильно - будет выдано сообщение и форма не закроется.
Рассмотрим параметры вызова даной функции:

* Заголовок - заголовок формы, на рисунке - синяя надпись "Тестовый документ №3 от 30.04.04";
* Кому, ОтКого, Копия - E-mail или список E-Mail`ов (через ",");
* Тема, Сообщение - соответствующие параметры письма;
* Запретить - какие поля запрещены для редактирования (на рисунке - поле Тема);
* БезФормы - если 1: форма не отображается и при правильных параметрах письмо отправится автоматически.


Следующая функция вызывает эту и если все прошло успешно - вызывает внешнюю обработку для небольшой предподготовки таблицы при печати и отправки ее:

Код: (1c)
// ======================================
// Запрос параметров, предподготовка таблицы и отправка почты
Функция глОтправитьПоПочтеРисунок(Таб, Знач Заголовок, Знач Кому, Знач ОтКого,
Знач Копия="", Знач Тема="", Знач Сообщение="",
Знач Запретить="", Знач БезФормы=0, Знач Масштаб=0) Экспорт
Перем Сп, Файл;

// Запрашиваем подтверждения параметров
Файл=глПараметрыОтправкиПочты(Заголовок,Кому,ОтКого,
Копия,Тема,Сообщение,Запретить,БезФормы);

Если ПустаяСтрока(Файл)=1 Тогда
Сообщить("Почта не была отправлена","!");
Возврат 0;
КонецЕсли;

// Устанавливаем значения для отправки и инициализируем отправку
Сп=СоздатьОбъект("СписокЗначений");
Сп.Установить("Таблица", Таб);
Сп.Установить("Масштаб", Масштаб);
ОткрытьФормуМодально("Отчет", Сп, КаталогИБ()+"ExtForms\ОтправитьКакРисунок.ert");

СТаб=СимволТабуляции;
глЗаписатьЛог("ОтправитьПоПочтеРисунок", Шаблон(
"ОтКого:[ОтКого]"+СТаб+"Кому:[Кому]"+СТаб+"Копия:[Копия]"+СТаб+"Тема:[Тема]"));
// В этот момент ушло на принтер, но возможно еще не обработалось
Сообщить("Отправляется. Ожидайте копию письма на адрес: "+ОтКого);
Возврат 1;
КонецФункции

Здесь уже большая функциональность перенесена на обработку. Она (обработка) вообще не открывается, только выполняет некоторые действия. Рассмортим параметры:

* Таб - Значение типа "Таблица", которую и будем печатать;
* Заголовок, Кому, ОтКого, Копия, Тема, Сообщение, Запретить, БезФормы - просто передаются в функцию глПараметрыОтправкиПочты и подробно рассмотрены в ней;
* Масштаб - масштаб печати таблицы. Если не задан - автомасштаб по ширине.


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

Код: (1c)
Перем Принтер;
// ======================================
Функция ПроверитьПараметр(Сп, Назв, ВсеОк, Обязат=1, Умолч="", Тип="")
Зн=Сп.Получить(Назв);
Если (ПустоеЗначение(Зн)=1) Тогда
Если (Обязат=1) Тогда
ВсеОк=0;
Сообщить(Шаблон("Не задан обязательный параметр: '[Назв]'"),"!");
Иначе
Зн=Умолч;
КонецЕсли;
КонецЕсли;
Если ПустаяСтрока(Тип)=0 Тогда
ТипЗн=ТипЗначенияСтр(Зн);
Если НРег(ТипЗн)<>НРег(Тип) Тогда
ВсеОк=0;
Сообщить(Шаблон("Для '[Назв]' не верен тип ('[ТипЗн]'). Ожидаемый тип: '[Тип]'"),"!");
КонецЕсли;
КонецЕсли;

Возврат Зн;
КонецФункции
// ======================================
Процедура ПриОткрытии()
// Проверим корректность вызова
Путь=""; Назв="";
РасположениеФайла(Путь, Назв);
Назв=СтрЗаменить(Назв,".ert","")+": ";
СтатусВозврата(0); // а вообще не нужно открывать!
ВсеОк=1;
Если Форма.МодальныйРежим()=0 Тогда
Сообщить(Назв+"Запустите обработку в модальном режиме!","!");
ВсеОк=0;
КонецЕсли;
Сп=Форма.Параметр;
Если ТипЗначенияСтр(Сп)<>"СписокЗначений" Тогда
Сообщить(Назв+"В качестве параметра необходим СписокЗначений!","!");
ВсеОк=0;
КонецЕсли;
Если ВсеОк=0 Тогда Возврат; КонецЕсли;

Таб=ПроверитьПараметр(Сп, "Таблица", ВсеОк, 1, "", "Таблица");
Масштаб=Число(ПроверитьПараметр(Сп, "Масштаб", ВсеОк, 0, -1));

Если ВсеОк=0 Тогда Возврат; КонецЕсли;
Состояние("Обработка таблицы...");
Таб.Область().ЦветТекста(0); // Все черное, кроме...
// Пробежимся по всем ячейкам и поменяем контроль=авто,
// т.е. красный цвет на отрицательных значениях
// на черный цвет
Для И1=1 По Таб.ВысотаТаблицы() Цикл
Для И2=1 По Таб.ШиринаТаблицы() Цикл
Обл=Таб.Область(И1,И2,И1,И2);
Если Обл.Контроль()=5 Тогда Обл.Контроль(1);
ИначеЕсли Обл.Контроль()=6 Тогда Обл.Контроль(3);
КонецЕсли;
КонецЦикла;
КонецЦикла;
// Чтобы параметры печати не путались с другими - даем им имя
Таб.Опции(,,,,"Как рисунок "+Принтер,,,);
// Только книжная ориентация
Таб.ПараметрыСтраницы(1,,,5,5,5,5,,,,,Принтер);
// Автомасштабирование по ширине, если явно не задан масштаб
Если Масштаб>0 Тогда Таб.ПараметрыСтраницы(,Масштаб,,,,,,,,,,);
Иначе Таб.ПараметрыСтраницы(,,,,,,,,,1,,); КонецЕсли;
// Только 1 экземпляр
Таб.ЭкземпляровНаСтранице(1);
Таб.КоличествоЭкземпляров(1);
// Вперед!
Таб.Напечатать(0);
КонецПроцедуры
Принтер="Send EMail";

Вот практически и все, что касается программы в 1С. Некоторые сервисные функции, которые не были описаны здесь, можно посмотреть в примере конфигурации. Таким образом ничего сложного здесь нет. Больше сложностей вызывает настройка системы для правильной работы. Выглядит отправленный документ приблизительно так:



Замечания в процессе эксплуатации


Сразу скажу - в боевом режиме система работает недолго (с 15.04.2004), но даже за это время были замечены некоторые "особенности" работы:

* Формат tiff оказался не таким уж стандартным. Потому пришлось его заменить на png. Сделать это нужно в двух местах: в суффиксе исходящего файла в скрипте (чтобы Postie правильно поставил его Content-Type:) и в настройках GS (параметр -sDEVICE=pngmono собственно и задает выходной формат файла). Можно заменить и на еще более стандартный jpeg, но при этом сильно вырастет размер файла. К сожалению gif уже не поддерживается в текущей версии GS (как я понял из документации - из-за возможных проблем с лицензированием этого формата). Можно добится поддержки gif, выдрав ее из исходников предыдущих версий и перекомпилировав текущую, но я пока этого не делал. Возникла мысль передавать в настроечном файле (%UserProfile%\SendMail\mail.ini) параметры, как отправлять изображения (jpeg, tif, png; color/mono; ...) и в скрипте динамически менять.
* PostScript шрифты, идущие в поставке GS, не так хорошо "вылизаны", как TrueType. Потому русские буквы выглядят жирнее англиских. Пока жалоб на это не было :-)
* В новой версии Postie у меня почему-то не работает ключ -bcc (ошибки не выдает, но и не отправляет по указанным адресам). Так и не разобрался - пришлось откатится на старую версию (POSTIE Version 4)
* Хотя ломать ничего и не пришлось, но все-таки мы нарушаем лицензию Postie, который "free for personal use". Может кто знает другую программу отправки почты из коммандной строки?



Благодарности


Моему любимому директору - за неуемный ум и новые интересные задания.
Вадиму Ханасюку - за неопубликованную здесь, но полезную компоненту SysInfo (получение каталога профиля пользователя по имени) и помощь в поиске нужного софта.
Всем сотрудникам, которые не мешали работать.
Information
  • Posted on 31.01.2010 20:51
  • Просмотры: 1684