Автоматическая выгрузка отчетов по расписанию — одна из самых популярных задач в администрировании и разработке на платформе 1С:Предприятие. Однако очень часто разработчики сталкиваются с ситуацией, когда код, который идеально работает в ручном режиме (через внешнюю обработку), напрочь отказывается выполняться в регламентном задании. При этом система выдает невнятные системные ошибки или просто молча завершает работу сбоем.
В этой статье мы подробно разберем, почему возникает такая проблема, проанализируем неочевидные архитектурные особенности платформы 1С и настроим отказоустойчивый код для регламентного задания, который гарантированно запишет файл в нужное место.
Давайте внимательно посмотрим на финальную строку в коде автора из первого сообщения темы: в самом конце там написано ключевое слово Показать (подразумевающее метод табличного документа Таблица.Показать()). Это самая критическая ошибка при проектировании серверного кода.
Разберем логику работы платформы. Любое регламентное задание выполняется в фоновом режиме в так называемом серверном контексте. У сервера 1С нет и не может быть графического интерфейса. Он выполняется как служба операционной системы Windows или Linux в изолированном пространстве.
Когда фоновое задание доходит до вызова метода Показать(), платформа пытается инициировать открытие окна на клиенте. Но клиента не существует! В результате сервер генерирует исключение: "Операция недоступна на сервере", и выполнение регламентного задания аварийно прерывается. Файл при этом может даже не успеть записаться, либо запишется, но статус задания в консоли всегда будет "Выполнено с ошибками".
Решение: Полностью исключить любые вызовы диалоговых окон, предупреждений, вопросов и методов визуализации, таких как Показать(), ОткрытьФорму() или Предупреждение(), из кода, предназначенного для работы в регламентных заданиях.
Проанализируем следующую строку кода, которая изначально использовалась автором:
ПутьКФайлу = "C:\Users\Desktop\Новая папка";
ИмяФайла = "Отчет от" + СОКРЛП(Формат(ТекущаяДата,"ДФ=yyyy-MM-dd-ЧЧ-мм-сс"))+ ".xls";
Файл = ПутьКФайлу + ИмяФайла;
При простом сложении этих строк переменная Файл получит значение:
"C:\Users\Desktop\Новая папкаОтчет от 2020-01-13-12-00-00.xls"
Как мы видим, операционная система воспримет часть строки Новая папкаОтчет от... как единое имя файла, расположенное в каталоге C:\Users\. Если папки с названием Users на диске C нет (или у сервера нет туда прав), запись завершится ошибкой.
Для решения этой проблемы необходимо четко контролировать наличие закрывающего слэша в пути каталога:
Файл = ПутьКФайлу + "\" + ИмяФайла;
А еще лучше использовать специализированные функции для работы с путями, если они доступны в вашей версии БСП, либо самостоятельно проверять наличие символа разделителя на конце строки перед объединением.
Даже после исправления слэша автор получил следующую ошибку: Ошибка при выполнении файловой операции ... -2147287037(0x80030003): Путь %1 не найден. Этот код ошибки (STG_E_PATHNOTFOUND) является стандартным системным сообщением Windows COM-объектов, указывающим на то, что указанного пути физически не существует в системе.
Почему же папка "C:\Users\Desktop\Новая папка" не находится сервером?
C:\Users\Desktop не существует. Рабочий стол конкретного пользователя находится по пути C:\Users\Имя_Пользователя\Desktop."Agile 1C Enterprise Server" (обычно это техническая учетная запись USR1CV8). У этого пользователя совершенно другой профиль в системе, и у него физически нет доступа к вашему Рабочему столу.C:\Users\... платформа будет искать на жестком диске серверной машины, а не на вашем персональном компьютере, с которого вы запустили конфигуратор.Решение: Никогда не использовать жестко прописанные пути (hardcode) к персональным папкам пользователей (Рабочий стол, Документы) в серверном коде.
В первоначальном коде автора запись выполнялась без указания второго параметра метода Записать. По умолчанию платформа 1С записывает табличный документ в своем внутреннем формате MXL, даже если вы принудительно указали расширение файла .xls в строке пути. При попытке открыть такой файл в Microsoft Excel пользователь получит предупреждение о повреждении структуры документа.
В последующих сообщениях автор исправил вызов на:
Таблица.Записать(Файл, ТипФайлаТабличногоДокумента.XLS);
Однако формат XLS (Excel 97-2003) накладывает жесткое ограничение на количество строк на одном листе — не более 65 536 строк. Если ваш отчет по маркетинговому исследованию цен окажется достаточно большим, регламентное задание прервется с ошибкой переполнения листа.
Рекомендация: Всегда используйте современный формат XLSX (Excel 2007 и выше), который поддерживает более миллиона строк, обеспечивает лучшее сжатие данных и стабильно читается всеми современными офисными программами.
Таблица.Записать(Файл, ТипФайлаТабличногоДокумента.XLSX);
Как справедливо заметили участники обсуждения на форуме, само регламентное задание может не запускаться по расписанию, если при его создании в конфигураторе не был установлен флаг Предопределенное.
Если этот признак не установлен, регламентное задание создается в базе данных как "шаблон", но его фактический экземпляр не запускается автоматически. В таком случае администратору системы необходимо создавать экземпляр задания вручную через консоль регламентных заданий в режиме "1С:Предприятие". Для автоматического старта сразу после обновления конфигурации базы данных обязательно ставьте этот флажок в свойствах объекта в конфигураторе.
Рассмотрим несколько надежных и архитектурно верных способов организации выгрузки отчетов в фоновом режиме.
Если ваша задача — сформировать отчет и передать его пользователям, то самым надежным решением будет сохранение файла во временный каталог сервера с последующей отправкой по электронной почте. Служба сервера 1С гарантированно имеет полные права на чтение и запись в своей временной директории, что исключает любые ошибки доступа.
Проанализируем пример кода для такого решения:
// Сформируем табличный документ по схеме компоновки данных (СКД)
Таблица = Новый ТабличныйДокумент;
СКД = Отчеты.МаркетинговоеИсследованиеЦен.ПолучитьМакет("Макет");
Настройки = СКД.НастройкиПоУмолчанию;
// Установим параметры отчета
ПараметрПериод = Настройки.ПараметрыДанных.Элементы.Найти("Период");
Если ПараметрПериод <> Неопределено Тогда
ПараметрПериод.Значение = ТекущаяДата();
ПараметрПериод.Использование = Истина;
КонецЕсли;
Расшифровка = Новый ДанныеРасшифровкиКомпоновкиДанных;
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СКД, Настройки, Расшифровка);
ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновки, , Расшифровка);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(Таблица);
ПроцессорВывода.Вывести(ПроцессорКомпоновкиДанных, Истина);
// Шаг 1. Получаем гарантированно доступный путь во временном каталоге сервера
ИмяФайла = "Отчет_цены_" + Формат(ТекущаяДата(), "ДФ=yyyy-MM-dd") + ".xlsx";
ПутьКВременномуФайлу = ПолучитьИмяВременногоФайла("xlsx");
// Шаг 2. Записываем табличный документ в формате XLSX
Таблица.Записать(ПутьКВременномуФайлу, ТипФайлаТабличногоДокумента.XLSX);
// Шаг 3. Отправляем сформированный файл по электронной почте (пример упрощенный)
// Здесь мы используем стандартные механизмы отправки почты 1С
Почта = Новый СистемныеПочтовыеПрофили;
// Настроить профиль отправки...
// Письмо = Новый ПочтовоеСообщение;
// Письмо.Вложения.Добавить(ПутьКВременномуФайлу, ИмяФайла);
// Почта.Отправить(Письмо);
// Шаг 4. Обязательно удаляем временный файл после отправки, чтобы не засорять диск сервера
Попытка
УдалитьФайлы(ПутьКВременномуФайлу);
Исключение
ЗаписьЖурналаРегистрации("РегламентноеЗадание.ВыгрузкаОтчета",
УровеньЖурналаРегистрации.Предупреждение, , ,
"Не удалось удалить временный файл: " + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
Если отчет необходимо складывать в общую папку в локальной сети компании для доступа других сотрудников, используйте сетевые пути в формате UNC (например, "\\server-storage\SharedReports\").
Важное условие безопасности: Учетная запись службы Windows, под которой запущен сервер 1С (тот самый USR1CV8), должна обладать правами на запись ("Запись", "Изменение") в эту сетевую папку на уровне общего доступа и безопасности NTFS. Прав вашей личной учетной записи Windows для этого недостаточно!
Посмотрим на пример кода с предварительной проверкой доступности каталога и автоматическим его созданием при необходимости:
СетевойКаталог = "\\server-storage\SharedReports\Marketing\";
ИмяФайла = "Отчет_" + Формат(ТекущаяДата(), "ДФ=yyyy-MM-dd") + ".xlsx";
ПолныйПуть = СетевойКаталог + ИмяФайла;
// Проверяем существование сетевой папки
КаталогНаДиске = Новый Файл(СетевойКаталог);
Если Не КаталогНаДиске.Существует() Тогда
Попытка
СоздатьКаталог(СетевойКаталог);
Исключение
ЗаписьЖурналаРегистрации("РегламентноеЗадание.ВыгрузкаОтчета",
УровеньЖурналаРегистрации.Ошибка, , ,
"Ошибка создания каталога: " + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Возврат; // Завершаем выполнение, так как папка недоступна
КонецПопытки;
КонецЕсли;
// Записываем файл
Попытка
Таблица.Записать(ПолныйПуть, ТипФайлаТабличногоДокумента.XLSX);
Исключение
ЗаписьЖурналаРегистрации("РегламентноеЗадание.ВыгрузкаОтчета",
УровеньЖурналаРегистрации.Ошибка, , ,
"Не удалось записать файл отчета на сетевой ресурс: " + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
Часто файлы пытаются сохранить на диск сервера просто из-за отсутствия альтернатив. Однако наиболее прогрессивным методом является сохранение сформированного отчета непосредственно в саму базу данных 1С — например, в регистр сведений или справочник "Присоединенные файлы".
Пользователи смогут открыть и скачать этот файл в любой момент времени из интерфейса программы (в том числе при работе через Веб-клиент в обычном браузере), при этом вам не придется настраивать сетевые права доступа на сервере приложений.
Для реализации этого подхода выполните следующие шаги:
// Сохраняем табличный документ во временный файл на сервере
ВременныйФайл = ПолучитьИмяВременногоФайла("xlsx");
Таблица.Записать(ВременныйФайл, ТипФайлаТабличногоДокумента.XLSX);
// Помещаем файл в двоичные данные
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ВременныйФайл);
// Создаем хранилище значения для записи в базу данных
ХранилищеФайла = Новый ХранилищеЗначения(ДвоичныеДанныеФайла, Новый СжатиеДанных(9));
// Записываем хранилище в справочник ХранимыеОтчеты или регистр сведений
НовыйОтчет = Справочники.ХранимыеОтчеты.СоздатьЭлемент();
НовыйОтчет.Наименование = "Отчет по ценам от " + Формат(ТекущаяДата(), "ДФ=dd.MM.yyyy HH:mm");
НовыйОтчет.ДанныеФайла = ХранилищеФайла;
НовыйОтчет.Расширение = "xlsx";
НовыйОтчет.Записать();
// Чистим временный файл на диске сервера
УдалитьФайлы(ВременныйФайл);
Используя эти паттерны программирования, мы полностью обезопасим наши регламентные задания от сбоев файловой системы, разграничения прав доступа и ограничений интерактивного интерфейса платформы 1С:Предприятие.