Как запустить внешний отчет СКД в регламентном задании на сервере 1С без ошибок

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

Анализ исходного алгоритма и его уязвимостей

Рассмотрим исходный код, который планируется запускать в регламентном задании:


Таб = Новый ТабличныйДокумент; 
ИмяФайлаОтчета = КаталогВременныхФайлов + "Report_123.tmp"; 
Отчет = Справочники.ВнешниеОбработки.НайтиПоНаименованию("Остатки склада+Стоимость"); 
ДвоичныеДанные = Отчет.ХранилищеВнешнейОбработки.Получить; 
ДвоичныеДанные.Записать(ИмяФайлаОтчета); 
НужныйОтчет = ВнешниеОтчеты.Создать(ИмяФайлаОтчета, ложь); 
СхемаКомпоновкиДанных = НужныйОтчет.ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных"); 
Настройки = СхемаКомпоновкиДанных.НастройкиПоУмолчанию; 
НачалоПериода = Настройки.ПараметрыДанных.Элементы.Найти("НачалоПериода"); 
НачалоПериода.Значение = НачалоДня(ТекущаяДата)-86400;
КонецПериода = Настройки.ПараметрыДанных.Элементы.Найти("КонецПериода"); 
КонецПериода.Значение = КонецДня(ТекущаяДата)-80000; 
Склад = Настройки.ПараметрыДанных.Элементы.Найти("Склад"); 
Склад.Значение = Справочники.Склады.НайтиПоКоду("000000343"); 

Расшифровка = Новый ДанныеРасшифровкиКомпоновкиДанных; 
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных; 
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, Расшифровка); 
ВнешниеПараметры = Новый Структура; 
ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных; 
ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновки, ВнешниеПараметры, Расшифровка); 

ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент; 
ПроцессорВывода.УстановитьДокумент(Таб); 
ПроцессорВывода.Вывести(ПроцессорКомпоновкиДанных, Истина); 

ИмяФайла = КаталогВременныхФайлов + ".xls"; 
Таб.Записать(ИмяФайла,ТипФайлаТабличногоДокумента.XLSX); 
Таб.Записать("C:\Users\123\Desktop\Обработки\Дебиторка .xls", ТипФайлаТабличногоДокумента.XLS);

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

Развенчание мифа: Нужен ли MS Excel на сервере?

В обсуждениях часто встречается мнение, что для работы метода Таб.Записать() в форматах XLS или XLSX на сервере обязательно должно быть установлено офисное приложение MS Excel. Это миф.

Начиная с платформы 1С:Предприятие 8.2, система использует собственный встроенный кроссплатформенный механизм для генерации файлов Microsoft Excel. Конвертация табличного документа выполняется силами самой платформы. Код будет успешно выполняться как на серверах под управлением Windows, так и под управлением Linux, без установки какого-либо стороннего программного обеспечения.

Правильная работа с временными файлами на сервере

В исходном коде допущены серьезные ошибки при формировании путей к временным файлам:

  1. Отсутствие скобок при вызове функции: Конструкция КаталогВременныхФайлов + "Report_123.tmp" ошибочна. КаталогВременныхФайлов() — это функция глобального контекста, и её вызов обязательно требует круглых скобок. На сервере такое обращение вызовет ошибку компиляции.
  2. Неверная конкатенация строк: Функция КаталогВременныхФайлов() возвращает путь, который уже заканчивается косой чертой (слешем). Попытка выполнить сложение строк КаталогВременныхФайлов() + ".xls" приведет к созданию пути вида C:\Windows\Temp\.xls. Файл без имени, состоящий только из расширения, в лучшем случае запишется некорректно, в худшем — система выдаст ошибку отсутствия доступа.
  3. Риск конфликта имен: При одновременном запуске нескольких фоновых заданий использование жестко прописанного имени файла (например, "Report_123.tmp") приведет к тому, что один поток перезапишет или заблокирует файл другого потока.
  4. Накопление мусора на диске: Созданные вручную файлы платформа не отслеживает. Со временем временная папка сервера переполнится, что приведет к деградации производительности всей ОС.

Для решения этих проблем необходимо использовать метод ПолучитьИмяВременногоФайла(). Платформа самостоятельно сгенерирует уникальное имя файла в системной директории и автоматически удалит его по завершении сеанса (начиная с версии 8.3.17, удаление неиспользуемых временных файлов происходит централизованно через 20 минут после закрытия сеанса или перезапуска рабочих процессов). Тем не менее, хорошим тоном является принудительное удаление файлов в конце процедуры.

Рассмотрим правильный пример инициализации путей:


ИмяФайлаОтчета = ПолучитьИмяВременногоФайла("tmp");
ИмяФайлаЭкспорта = ПолучитьИмяВременногоФайла("xlsx");

Безопасный режим и обход защиты от опасных действий

Автор использует метод ВнешниеОтчеты.Создать(ИмяФайлаОтчета, Ложь). Второй параметр Ложь указывает платформе, что отчет должен быть подключен в небезопасном режиме (с полными правами к базе данных и операционной системе).

Здесь кроются две опасности:

Как решить эту проблему правильно?

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

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


ОписаниеЗащиты = Новый ОписаниеЗащитыОтОпасныхДействий;
ОписаниеЗащиты.ПредупреждатьОбОпасныхДействиях = Ложь;
НужныйОтчет = ВнешниеОтчеты.Создать(ИмяФайлаОтчета, Ложь, ОписаниеЗащиты);

Оптимизация установки параметров СКД

В исходном коде установка параметров СКД выполняется через поиск элементов в коллекции настроек:


Склад = Настройки.ПараметрыДанных.Элементы.Найти("Склад");
Склад.Значение = Справочники.Склады.НайтиПоКоду("000000343");

Этот подход крайне опасен. Если в схеме компоновки данных (или в текущем варианте настроек отчета) параметр "Склад" будет удален, метод Найти() вернет значение Неопределено. Следующая строка кода вызовет критическую ошибку выполнения: "Значение поля 'Значение' не может быть записано".

Безопаснее и лаконичнее использовать предопределенный метод компоновщика настроек УстановитьЗначениеПараметра(). Проанализируем правильный вариант:


Настройки.ПараметрыДанных.УстановитьЗначениеПараметра("НачалоПериода", НачалоДня(ТекущаяДата) - 86400);
Настройки.ПараметрыДанных.УстановитьЗначениеПараметра("КонецПериода", КонецДня(ТекущаяДата) - 80000);

СсылкаСклад = Справочники.Склады.НайтиПоКоду("000000343");
Если ЗначениеЗаполнено(СсылкаСклад) Тогда
    Настройки.ПараметрыДанных.УстановитьЗначениеПараметра("Склад", СсылкаСклад);
КонецЕсли;

Данный метод автоматически инициализирует параметр в коллекции (если он доступен в схеме) и присваивает ему переданное значение, защищая выполнение регламентного задания от непредвиденных падений.

Права доступа Агента сервера 1С и кроссплатформенность

При записи готового файла по фиксированному пути (например, "C:\Users\123\Desktop\Обработки\Дебиторка .xls") необходимо учитывать особенности сетевой архитектуры и операционной системы сервера:

  1. Кроссплатформенность путей: Если сервер 1С работает под управлением ОС Linux, пути формата Windows (с использованием обратных слешей и букв дисков вроде C:\) будут восприниматься системой как некорректные символы, что вызовет ошибку записи. В Linux-среде необходимо монтировать сетевые папки в единую файловую систему и использовать пути формата Unix: /mnt/share/debitorka.xlsx.
  2. Права системной службы: По умолчанию служба "Агент сервера 1С:Предприятия" (пользователь usr1cv8) запускается под локальной учетной записью компьютера. У этой учетной записи нет прав на запись данных в личные папки пользователей (например, в профиль C:\Users\123\Desktop\...) или на сетевые ресурсы других серверов. Для корректной выгрузки файлов необходимо:
    • Выгружать файлы в общие папки, к которым предоставлен полный доступ (чтение/запись) для локального пользователя сервера 1С.
    • Если файл необходимо передать в сетевую папку другого сервера по UNC-пути (например, \\server-share\folder\file.xlsx), службу Агента 1С на сервере требуется перенастроить на запуск от имени доменной учетной записи, наделенной соответствующими правами в сети Active Directory.
  3. Отказ от хардкода: Прописывать абсолютные пути выгрузки файлов непосредственно в коде конфигурации — грубая ошибка проектирования. Пути выгрузки должны храниться в константах или справочниках настроек системы, чтобы администратор мог оперативно перенаправить экспорт файлов без редактирования конфигурации.

Безопасный поиск внешней обработки в справочнике

Поиск внешней обработки по наименованию Справочники.ВнешниеОбработки.НайтиПоНаименованию("Остатки склада+Стоимость") таит в себе опасность. Метод поиска по наименованию не гарантирует уникальности. Кроме того, если кто-то переименует элемент справочника в режиме "1С:Предприятие", код вернет пустую ссылку. Попытка получить двоичные данные из пустой ссылки приведет к ошибке выполнения.

Необходимо обязательно добавлять проверку на заполненность полученной ссылки перед обращением к её реквизитам:


ОтчетСсылка = Справочники.ВнешниеОбработки.НайтиПоНаименованию("Остатки склада+Стоимость");
Если ОтчетСсылка.Пустая() Тогда
    ЗаписьЖурналаРегистрации("РегламентныеЗадания.ВыгрузкаОтчета",
        УровеньЖурналаРегистрации.Ошибка, , ,
        "Не найден элемент справочника 'Внешние обработки' с наименованием 'Остатки склада+Стоимость'!");
    Возврат;
КонецЕсли;

Итоговый надежный шаблон кода для регламентного задания

Сведем все рекомендации воедино и разработаем отказоустойчивый шаблон кода, готовый к запуску на сервере в регламентном задании:


Процедура ВыполнитьВыгрузкуОтчетаНаСервере() Экспорт

    // 1. Поиск обработки с проверкой на существование
    НаименованиеОбработки = "Остатки склада+Стоимость";
    ОтчетСсылка = Справочники.ВнешниеОбработки.НайтиПоНаименованию(НаименованиеОбработки);
    
    Если ОтчетСсылка.Пустая() Тогда
        ЗаписьЖурналаРегистрации("РегламентныеЗадания.ВыгрузкаОтчета",
            УровеньЖурналаРегистрации.Ошибка, , ,
            "Не найден элемент справочника Внешние обработки: " + НаименованиеОбработки);
        Возврат;
    КонецЕсли;
    
    // 2. Инициализация уникальных временных файлов
    ИмяФайлаОтчета = ПолучитьИмяВременногоФайла("tmp");
    ИмяФайлаЭкспорта = ПолучитьИмяВременногоФайла("xlsx");
    
    Попытка
        // Извлекаем двоичные данные обработки из хранилища
        ДвоичныеДанные = ОтчетСсылка.ХранилищеВнешнейОбработки.Получить();
        Если ДвоичныеДанные = Неопределено Тогда
            ВызватьИсключение "Хранилище внешней обработки пустое!";
        КонецЕсли;
        ДвоичныеДанные.Записать(ИмяФайлаОтчета);
        
        // Подключаем внешний отчет с отключением предупреждений защиты
        ОписаниеЗащиты = Новый ОписаниеЗащитыОтОпасныхДействий;
        ОписаниеЗащиты.ПредупреждатьОбОпасныхДействиях = Ложь;
        
        НужныйОтчет = ВнешниеОтчеты.Создать(ИмяФайлаОтчета, Ложь, ОписаниеЗащиты);
        СхемаКомпоновкиДанных = НужныйОтчет.ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
        Настройки = СхемаКомпоновкиДанных.НастройкиПоУмолчанию;
        
        // 3. Безопасная установка параметров СКД
        Настройки.ПараметрыДанных.УстановитьЗначениеПараметра("НачалоПериода", НачалоДня(ТекущаяДата) - 86400);
        Настройки.ПараметрыДанных.УстановитьЗначениеПараметра("КонецПериода", КонецДня(ТекущаяДата) - 80000);
        
        СсылкаСклад = Справочники.Склады.НайтиПоКоду("000000343");
        Если НЕ СсылкаСклад.Пустая() Тогда
            Настройки.ПараметрыДанных.УстановитьЗначениеПараметра("Склад", СсылкаСклад);
        КонецЕсли;
        
        // 4. Инициализация процессоров компоновки данных
        Расшифровка = Новый ДанныеРасшифровкиКомпоновкиДанных;
        КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
        МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, Расшифровка);
        
        ВнешниеПараметры = Новый Структура;
        ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных;
        ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновки, ВнешниеПараметры, Расшифровка);
        
        Таб = Новый ТабличныйДокумент;
        ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
        ПроцессорВывода.УстановитьДокумент(Tab);
        ПроцессорВывода.Вывести(ПроцессорКомпоновкиДанных, Истина);
        
        // Записываем сформированный документ во временный файл формата XLSX
        Таб.Записать(ИмяФайлаЭкспорта, ТипФайлаТабличногоДокумента.XLSX);
        
        // 5. Перенос файла в целевой каталог с обработкой прав доступа
        // Рекомендуется получать путь из констант или настроек, а не прописывать в коде
        ПутьКЦелевомуКаталогу = "C:\Shares\Reports\"; // Убедитесь, что у службы 1С есть права на запись сюда
        ИмяИтоговогоФайла = ПутьКЦелевомуКаталогу + "Дебиторка.xlsx";
        
        ФайлНаДиске = Новый Файл(ИмяФайлаЭкспорта);
        Если ФайлНаДиске.Существует() Тогда
            ПереместитьФайл(ИмяФайлаЭкспорта, ИмяИтоговогоФайла);
        КонецЕсли;
        
    Исключение
        // Логируем все ошибки выполнения в Журнал Регистрации
        ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
        ЗаписьЖурналаРегистрации("РегламентныеЗадания.ВыгрузкаОтчета",
            УровеньЖурналаРегистрации.Ошибка, , ,
            "Ошибка при формировании отчета: " + ТекстОшибки);
    КонецПопытки;
    
    // 6. Обязательная очистка временных файлов
    Попытка
        УдалитьФайлы(ИмяФайлаОтчета);
        УдалитьФайлы(ИмяФайлаЭкспорта);
    Исключение
        // Ошибка удаления не должна прерывать выполнение процесса
    КонецПопытки;

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

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

← На главную