Запись файлов из кода 1С 8.3: табличный документ, двоичные данные, диалог и расписание
Содержание
Программно «Сохранить как» в 1С делают в два хода. На сервере формируете файл методом Записать() (у табличного документа, двоичных данных или текстового документа) и кладёте результат во временное хранилище. На клиенте отдаёте его пользователю через ПолучитьФайлССервераАсинх() (платформа 8.3.18 и новее) или НачатьПолучениеФайлаССервера() на более ранних релизах.
Такая схема одинаково работает в тонком клиенте, веб-клиенте и при работе через интернет . Файл, записанный прямо на сервере, лежит на диске сервера, а не на компьютере бухгалтера. Ниже код для печатных форм, отчётов, произвольных файлов и выгрузки по расписанию. Все примеры на встроенном языке платформы 8.3 с интерфейсом «Такси».
Как записать табличный документ в PDF, Excel и другие форматы
Отчёт, печатная форма, макет — всё это объект ТабличныйДокумент. У него есть метод Записать(ИмяФайла, ТипФайла). Второй параметр — значение системного перечисления ТипФайлаТабличногоДокумента.
| Значение | Что получится | Когда брать |
|---|---|---|
PDF | PDF-документ | печатные формы для отправки контрагенту |
XLSX | книга Excel | отчёты, которые будут дорабатывать |
XLS | старый формат Excel | только если адресат не открывает XLSX |
DOCX | документ Word | письма, договоры из макетов |
ODS | таблица LibreOffice | для пользователей свободных офисных пакетов |
MXL | родной формат 1С | для обмена между базами 1С |
HTML | веб-страница | публикация, вложение в письмо |
TXT, ANSITXT | текст | простые выгрузки без форматирования |
Базовый вариант для печатной формы выглядит так. Функция серверная, вызывается из формы:
&НаСервере
Функция ПечатнаяФормаВPDF()
ТабДок = СформироватьПечатнуюФорму(); // ваша функция, возвращает ТабличныйДокумент
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("pdf");
ТабДок.Записать(ИмяВременногоФайла, ТипФайлаТабличногоДокумента.PDF);
Данные = Новый ДвоичныеДанные(ИмяВременногоФайла);
УдалитьФайлы(ИмяВременногоФайла);
Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
КонецФункции
Три момента, на которых чаще всего спотыкаются:
- 🗂️
ПолучитьИмяВременногоФайла("pdf")даёт уникальное имя в каталоге временных файлов. Два пользователя, печатающих одновременно, не перезапишут файлы друг друга. - 🧹
УдалитьФайлы()сразу после чтения. Иначе каталог временных файлов на сервере за год распухает до гигабайтов. - 🔑
УникальныйИдентификаторформы во втором параметре хранилища. Данные живут, пока открыта форма, и удаляются при её закрытии.
Если печатная форма собирается типовым механизмом печати в конфигурации на БСП, не дублируйте её код. Вызовите серверную процедуру печати объекта, возьмите из результата табличный документ и запишите его так же, как выше.
Перед записью можно подправить параметры страницы: ТабДок.ОриентацияСтраницы = ОриентацияСтраницы.Ландшафт, ТабДок.АвтоМасштаб = Истина. На PDF они влияют напрямую: что не помещается по ширине, уйдёт на следующий лист.
Отчёт на системе компоновки данных
Для регламентной выгрузки отчёт формируют без формы. Табличный документ получают из схемы компоновки, а затем записывают тем же методом Записать():
Схема = Отчеты.ОстаткиТоваров.ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
Компоновщик = Новый КомпоновщикМакетаКомпоновкиДанных;
Макет = Компоновщик.Выполнить(Схема, Схема.НастройкиПоУмолчанию);
Процессор = Новый ПроцессорКомпоновкиДанных;
Процессор.Инициализировать(Макет);
ТабДок = Новый ТабличныйДокумент;
Вывод = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
Вывод.УстановитьДокумент(ТабДок);
Вывод.Вывести(Процессор);
Имя отчёта ОстаткиТоваров здесь условное, подставьте своё. Если нужны не настройки по умолчанию, а сохранённый вариант с отборами, загрузите его настройки в компоновщик настроек и передайте в Выполнить() их.
Двоичные данные, потоки и текстовые файлы
Не всё, что нужно сохранить, — табличный документ. Вложение из справочника, картинка, XML от банка, CSV для маркетплейса — это двоичные данные или текст.
Двоичные данные
ДвоичныеДанные пишутся одной строкой:
Данные = ХранилищеФайла.Получить(); // из реквизита типа ХранилищеЗначения
Данные.Записать(ПолноеИмяФайла);
Из строки двоичные данные получают функцией ПолучитьДвоичныеДанныеИзСтроки(Текст, КодировкаТекста.UTF8). Из Base64 — ПолучитьДвоичныеДанныеИзBase64Строки(). Так обычно разбирают ответы веб-сервисов, где файл приходит строкой.
Потоки
Начиная с 8.3.9 в платформе есть потоки: ФайловыйПоток, ПотокВПамяти. Они нужны, когда файл большой и держать его целиком в памяти рабочего процесса дорого.
&НаСервере
Процедура СкопироватьВФайл(Данные, ПолноеИмяФайла)
ПотокЗаписи = Новый ФайловыйПоток(ПолноеИмяФайла, РежимОткрытияФайла.Создать);
ПотокЧтения = Данные.ОткрытьПотокДляЧтения();
ПотокЧтения.КопироватьВ(ПотокЗаписи);
ПотокЧтения.Закрыть();
ПотокЗаписи.Закрыть();
КонецПроцедуры
Закрывайте поток всегда, в том числе при ошибке. Незакрытый ФайловыйПоток держит файл заблокированным, пока не завершится рабочий процесс. Следующая попытка записи упадёт, а удалить файл не даст Windows.
Текст, CSV и JSON
Для построчной записи берите ЗаписьТекста, она сама ставит переводы строк:
Запись = Новый ЗаписьТекста(ПолноеИмяФайла, КодировкаТекста.UTF8);
Запись.ЗаписатьСтроку("Артикул;Наименование;Остаток");
Для Каждого Строка Из ТаблицаОстатков Цикл
Запись.ЗаписатьСтроку(СтрШаблон("%1;%2;%3",
Строка.Артикул, Строка.Наименование, Формат(Строка.Остаток, "ЧГ=0; ЧН=0")));
КонецЦикла;
Запись.Закрыть();
Формат(..., "ЧГ=0") убирает пробелы-разделители разрядов. Без него остаток 12500 попадёт в CSV как «12 500», и Excel сочтёт его текстом.
JSON пишут через ЗаписьJSON: ОткрытьФайл(ПолноеИмяФайла), затем ЗаписатьJSON(Запись, Структура), затем Закрыть().
Где выполняется запись: на клиенте или на сервере
Главный вопрос, который решают до написания кода, — на каком компьютере окажется файл. Код в &НаСервере пишет на диск сервера 1С. Код в &НаКлиенте пишет на компьютер пользователя.
| Сценарий | Где писать | Чем передать |
|---|---|---|
| Пользователь нажал кнопку «Сохранить в PDF» | сформировать на сервере | ПолучитьФайлССервераАсинх() |
| Файловая база, тонкий клиент, путь на локальном диске | можно прямо на клиенте | Данные.Записать() на клиенте |
| Веб-клиент | только через передачу файла | браузер скачивает сам |
| Выгрузка в сетевую папку по расписанию | на сервере | ничего, файл сразу на месте |
| Несколько файлов пользователю | на сервере, упаковать в ZIP | один архив вместо десятка диалогов |
Клиентская часть для кнопки печати:
&НаКлиенте
Асинх Процедура СохранитьВPDF(Команда)
Адрес = ПечатнаяФормаВPDF();
ИмяФайла = "Акт_" + СтрЗаменить(Объект.Номер, "/", "-") + ".pdf";
Результат = Ждать ПолучитьФайлССервераАсинх(Адрес, ИмяФайла);
Если Результат = Неопределено Тогда
Возврат; // пользователь отменил сохранение
КонецЕсли;
КонецПроцедуры
Для платформы ниже 8.3.18 ключевых слов Асинх и Ждать нет. Тот же результат даёт вызов с описанием оповещения:
Оповещение = Новый ОписаниеОповещения("ПослеСохраненияФайла", ЭтотОбъект);
НачатьПолучениеФайлаССервера(Оповещение, Адрес, ИмяФайла);
Синхронный ПолучитьФайл() при режиме модальности «Не использовать» вызывает исключение. В современных конфигурациях этот режим включён почти везде, поэтому старый код из интернета с ПолучитьФайл() придётся переписать.
Несколько файлов пакуйте в архив на сервере: Новый ЗаписьZipФайла(ИмяАрхива), затем Добавить(ИмяФайла) для каждого файла и Записать(). Пользователь получит один диалог вместо пяти.
Диалог выбора файла и сборка пути
Когда пользователь должен сам выбрать папку и имя, используйте ДиалогВыбораФайла в режиме сохранения. Работает в тонком клиенте; в веб-клиенте нужно расширение работы с файлами.
&НаКлиенте
Процедура ВыбратьКудаСохранить(Команда)
Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Сохранение);
Диалог.Заголовок = "Куда сохранить отчёт";
Диалог.Фильтр = "Книга Excel (*.xlsx)|*.xlsx|Документ PDF (*.pdf)|*.pdf";
Диалог.ПолноеИмяФайла = "Остатки_" + Формат(ТекущаяДата(), "ДФ=yyyyMMdd") + ".xlsx";
Диалог.Показать(Новый ОписаниеОповещения("ПослеВыбораПути", ЭтотОбъект));
КонецПроцедуры
&НаКлиенте
Процедура ПослеВыбораПути(ВыбранныеФайлы, ДополнительныеПараметры) Экспорт
Если ВыбранныеФайлы = Неопределено Тогда
Возврат;
КонецЕсли;
ПолныйПуть = ВыбранныеФайлы[0];
Расширение = НРег(Новый Файл(ПолныйПуть).Расширение); // ".xlsx" или ".pdf"
Адрес = ОтчетВФайлНаСервере(Расширение); // сервер выбирает тип по расширению
Данные = ПолучитьИзВременногоХранилища(Адрес);
Данные.Записать(ПолныйПуть);
КонецПроцедуры
Для сборки путей в коде пригодятся:
- 📁
КаталогВременныхФайлов()— временный каталог текущего компьютера (клиента или сервера, смотря где выполняется код). - ➗
ПолучитьРазделительПути()—\в Windows и/в Linux. Сервер 1С на Linux не поймёт путь, склеенный через обратный слеш. - 🔍
Новый Файл(Путь).Существует()— проверка каталога перед записью. - 🏗️
СоздатьКаталог(Путь)— создаёт каталог вместе с недостающими родительскими.
Чистим имя файла
Номер документа и наименование контрагента почти всегда содержат символы, запрещённые в именах файлов Windows: \ / : * ? " < > |. Простая функция замены:
&НаКлиентеНаСервереБезКонтекста
Функция БезопасноеИмя(Знач Имя)
Запрещенные = "\/:*?""<>|";
Для Сч = 1 По СтрДлина(Запрещенные) Цикл
Имя = СтрЗаменить(Имя, Сред(Запрещенные, Сч, 1), "_");
КонецЦикла;
Возврат СокрЛП(Имя);
КонецФункции
Счёт «ООО “Ромашка” №12/3» превратится в «ООО Ромашка №12_3» и запишется без ошибки.
Ошибки при программном сохранении и как их перехватить
Запись файла — операция с внешней средой, она падает по причинам вне кода. Оборачивайте её в Попытка…Исключение и записывайте подробности в журнал регистрации:
Попытка
ТабДок.Записать(ПолноеИмяФайла, ТипФайлаТабличногоДокумента.XLSX);
Исключение
ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
ЗаписьЖурналаРегистрации("Выгрузка.ЗаписьФайла",
УровеньЖурналаРегистрации.Ошибка, , , ТекстОшибки);
ВызватьИсключение КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
Пользователь увидит краткое сообщение, а администратор найдёт полный стек в журнале регистрации по имени события.
Отдельно об отмене. Если пользователь закрыл диалог, асинхронный метод возвращает Неопределено. Это не ошибка, и выводить сообщение «Не удалось сохранить» в этом случае не нужно.
Сохранение файлов по расписанию: регламентное задание
Ежедневная выгрузка остатков в папку обмена, PDF реестра для руководителя к 9 утра — это регламентное задание. Код задания выполняется на сервере без формы и без пользователя, поэтому диалогов и передачи на клиент тут нет.
Порядок настройки:
- В Конфигураторе или в расширении создайте серверный общий модуль и экспортную процедуру выгрузки.
- Добавьте объект метаданных «Регламентное задание» и укажите эту процедуру в его имени метода.
- Задайте расписание. В конфигурациях на БСП его удобно менять из режима предприятия:
Администрирование → Обслуживание → Регламентные и фоновые задания.
Сама процедура:
Процедура ВыгрузитьОстаткиВПапку() Экспорт
Каталог = "\\fileserver\exchange\ostatki\";
Метка = Формат(ТекущаяДатаСеанса(), "ДФ=yyyyMMdd_HHmm");
ИмяЧерновика = Каталог + "ostatki_" + Метка + ".tmp";
ИмяИтоговое = Каталог + "ostatki_" + Метка + ".xlsx";
Попытка
ТабДок = СформироватьОтчетОстатков();
ТабДок.Записать(ИмяЧерновика, ТипФайлаТабличногоДокумента.XLSX);
ПереместитьФайл(ИмяЧерновика, ИмяИтоговое);
Исключение
ЗаписьЖурналаРегистрации("Выгрузка остатков",
УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
Приём с черновиком .tmp и ПереместитьФайл() защищает принимающую сторону. Скрипт или сервис, который забирает файлы по маске *.xlsx, никогда не схватит наполовину записанный файл.
Повторный ВызватьИсключение в конце нужен, чтобы задание завершилось со статусом ошибки. Тогда сбой виден в списке регламентных заданий, а не теряется молча.
В файловом варианте базы регламентные задания выполняет один из запущенных сеансов клиента (или веб-сервер, на котором опубликована база). Сетевой путь должен быть доступен с того компьютера, где работает этот сеанс, и от имени его пользователя Windows.
Для клиент-серверной базы проверьте, что учётная запись службы сервера 1С имеет права на запись в \\fileserver\exchange. Это самая частая причина, по которой задание вручную из Конфигуратора отрабатывает, а по расписанию падает.
Частые вопросы
Можно ли сохранить табличный документ без временного файла?
Записать() принимает поток, например ПотокВПамяти. Проверьте сигнатуру метода в синтакс-помощнике своей версии. Вариант с временным файлом работает на любом релизе 8.3.Почему в XLSX слетает ширина колонок и формат чисел?
Как сохранить сразу несколько печатных форм в один PDF?
ТабличныйДокумент: выводите формы по очереди и вставляйте горизонтальный разделитель страниц между ними. Затем запишите один раз в PDF.Можно ли записать файл прямо на компьютер пользователя из регламентного задания?
Как задать имя файла по умолчанию, но дать пользователю его поменять?
ПолучитьФайлССервераАсинх(). Платформа покажет диалог с этим именем, а пользователь сможет выбрать другую папку и исправить его.