Загрузка данных
Отличный код! Он написан очень грамотно, низкоуровневое формирование XLSX через сборку ZIP-архива — это отличное решение для высокой производительности и экономии памяти.
При переносе этого кода в регламентное задание нам нужно решить **главную проблему**: в коде обработки путь сохранения выбирается пользователем интерактивно (`ДиалогВыбораФайла`), а сам файл передается на клиент через временное хранилище (`ПолучитьФайл`). У регламентного задания нет интерфейса и нет клиента, оно выполняется на сервере. Поэтому файл нужно сохранять либо в сетевую папку (к которой у пользователя, под которым запущен сервер 1С, есть права), либо на FTP, либо отправлять по почте.
Ниже готовый код серверного общего модуля. В нем полностью убран интерактив, удалены директивы компиляции (в общих модулях они не нужны), а логика получения файла заменена на прямое сохранение на сервере.
### Шаг 1. Создай общий модуль
Создай общий модуль, например, `ЦФ_РегламентныеЗадания`.
Поставь свойства: **Сервер = Истина**, остальные флажки (Клиент, Внешнее соединение и т.д.) — **Ложь**.
Помести в него следующий код:
```bsl
#Область ОсновнойВызов
// Основная процедура, которую нужно указать в метаданных Регламентного Задания
Процедура ВыгрузитьКонтрагентовВXLSX() Экспорт
ИмяСобытияЖР = "Регламентное задание.Выгрузка контрагентов";
// ВАЖНО: Так как диалога выбора файла нет, путь должен быть серверным.
// Это может быть сетевой путь вроде "\\server\share\export.xlsx"
// или локальный путь на сервере 1С.
// Рекомендуется вынести этот путь в константу или настройки БСП.
ПутьКФайлу = "C:\Выгрузка\Контрагенты_" + Формат(ТекущаяДата(), "ДФ=yyyy-MM-dd") + ".xlsx";
// Проверим и создадим каталог назначения, если его нет
ФайлНазначения = Новый Файл(ПутьКФайлу);
Попытка
СоздатьКаталог(ФайлНазначения.Путь);
Исключение
ТекстОшибки = "Не удалось создать каталог для выгрузки: " + ФайлНазначения.Путь;
ЗаписьЖурналаРегистрации(ИмяСобытияЖР, УровеньЖурналаРегистрации.Ошибка,,, ТекстОшибки);
ВызватьИсключение ТекстОшибки;
КонецПопытки;
Попытка
// Запуск формирования
КоличествоВыгруженных = СформироватьXLSXНаСервере(ПутьКФайлу);
// Логируем успешное выполнение в Журнал Регистрации БСП
ТекстЛога = СтрШаблон("Успешно выгружено контрагентов: %1 в файл: %2",
Формат(КоличествоВыгруженных, "ЧГ=0"), ПутьКФайлу);
ЗаписьЖурналаРегистрации(ИмяСобытияЖР,
УровеньЖурналаРегистрации.Информация,,, ТекстЛога);
Исключение
// Логируем ошибку для администратора БСП
ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
ЗаписьЖурналаРегистрации(ИмяСобытияЖР,
УровеньЖурналаРегистрации.Ошибка,,, ТекстОшибки);
// Пробрасываем исключение, чтобы БСП зафиксировала статус "Ошибка" для регламентного задания
ВызватьИсключение "Ошибка при выполнении регламентного задания выгрузки: " + ТекстОшибки;
КонецПопытки;
КонецПроцедуры
#КонецОбласти
#Область СлужебныеПроцедурыИФункции
Функция СформироватьXLSXНаСервере(Знач ПутьКФайлу)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| нсиКонтрагенты.ИНН КАК ИНН,
| нсиКонтрагенты.КПП КАК КПП,
| нсиКонтрагенты.Наименование КАК Наименование,
| нсиКонтрагенты.ПолноеНаименование КАК ПолноеНаименование,
| нсиКонтрагенты.Ссылка КАК Ссылка
|ПОМЕСТИТЬ Вт_ОсновныеДанные
|ИЗ
| Справочник.нсиКонтрагенты КАК нсиКонтрагенты
|ГДЕ
| НЕ нсиКонтрагенты.ПометкаУдаления
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| МАКСИМУМ(нсиИзмененныеПоЗаявкамОбъекты.ДатаИзменения) КАК ДатаСоздания,
| нсиИзмененныеПоЗаявкамОбъекты.Объект КАК Объект
|ПОМЕСТИТЬ Вт_ДатаСоздания
|ИЗ
| РегистрСведений.нсиИзмененныеПоЗаявкамОбъекты КАК нсиИзмененныеПоЗаявкамОбъекты
|ГДЕ
| нсиИзмененныеПоЗаявкамОбъекты.Создание
| И нсиИзмененныеПоЗаявкамОбъекты.Объект В
| (ВЫБРАТЬ
| Вт_ОсновныеДанные.Ссылка КАК Ссылка
| ИЗ
| Вт_ОсновныеДанные КАК Вт_ОсновныеДанные)
|
|СГРУППИРОВАТЬ ПО
| нсиИзмененныеПоЗаявкамОбъекты.Объект
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| МАКСИМУМ(нсиИзмененныеПоЗаявкамОбъекты.ДатаИзменения) КАК ДатаИзменения,
| нсиИзмененныеПоЗаявкамОбъекты.Объект КАК Объект
|ПОМЕСТИТЬ Вт_ДатаИзменения
|ИЗ
| РегистрСведений.нсиИзмененныеПоЗаявкамОбъекты КАК нсиИзмененныеПоЗаявкамОбъекты
|ГДЕ
| нсиИзмененныеПоЗаявкамОбъекты.Объект В
| (ВЫБРАТЬ
| Вт_ОсновныеДанные.Ссылка КАК Ссылка
| ИЗ
| Вт_ОсновныеДанные КАК Вт_ОсновныеДанные)
| И нсиИзмененныеПоЗаявкамОбъекты.Изменение
|
|СГРУППИРОВАТЬ ПО
| нсиИзмененныеПоЗаявкамОбъекты.Объект
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Вт_ОсновныеДанные.ИНН КАК ИНН,
| Вт_ОсновныеДанные.КПП КАК КПП,
| Вт_ОсновныеДанные.Наименование КАК Наименование,
| Вт_ОсновныеДанные.ПолноеНаименование КАК ПолноеНаименование,
| Вт_ДатаИзменения.ДатаИзменения КАК ДатаИзменения,
| Вт_ДатаСоздания.ДатаСоздания КАК ДатаСоздания
|ИЗ
| Вт_ОсновныеДанные КАК Вт_ОсновныеДанные
| Левое Соединение Вт_ДатаСоздания КАК Вт_ДатаСоздания
| ПО Вт_ОсновныеДанные.Ссылка = Вт_ДатаСоздания.Объект
| Левое Соединение Вт_ДатаИзменения КАК Вт_ДатаИзменения
| ПО Вт_ОсновныеДанные.Ссылка = Вт_ДатаИзменения.Объект";
КаталогXLSX = КаталогВременныхФайлов()
+ "ВыгрузкаКонтрагентовXLSX_"
+ СтрЗаменить(Строка(Новый УникальныйИдентификатор), "-", "")
+ "\";
ИмяАрхива = ПолучитьИмяВременногоФайла("xlsx");
Попытка
СоздатьСтруктуруКаталоговXLSX(КаталогXLSX);
ЗаписатьСлужебныеФайлыXLSX(КаталогXLSX);
Количество = ЗаписатьЛистXLSX(
КаталогXLSX + "xl\worksheets\sheet1.xml",
Запрос.Выполнить().Выбрать());
Архив = Новый ЗаписьZipФайла(ИмяАрхива);
Архив.Добавить(
КаталогXLSX + "*",
РежимСохраненияПутейZIP.СохранятьОтносительныеПути,
РежимОбработкиПодкаталоговZIP.ОбрабатыватьРекурсивно);
Архив.Записать();
// Изменение: Вместо помещения во временное хранилище
// мы сразу копируем готовый архив по конечному пути назначения
КопироватьФайл(ИмяАрхива, ПутьКФайлу);
Исключение
УдалитьВременныеФайлыXLSX(КаталогXLSX, ИмяАрхива);
ВызватьИсключение;
КонецПопытки;
УдалитьВременныеФайлыXLSX(КаталогXLSX, ИмяАрхива);
Возврат Количество;
КонецФункции
Процедура СоздатьСтруктуруКаталоговXLSX(КаталогXLSX)
СоздатьКаталог(КаталогXLSX);
СоздатьКаталог(КаталогXLSX + "_rels");
СоздатьКаталог(КаталогXLSX + "xl");
СоздатьКаталог(КаталогXLSX + "xl\_rels");
СоздатьКаталог(КаталогXLSX + "xl\worksheets");
КонецПроцедуры
Процедура ЗаписатьСлужебныеФайлыXLSX(КаталогXLSX)
Содержимое =
"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
|<Types xmlns=""http://schemas.openxmlformats.org/package/2006/content-types"">
|<Default Extension=""rels"" ContentType=""application/vnd.openxmlformats-package.relationships+xml""/>
|<Default Extension=""xml"" ContentType=""application/xml""/>
|<Override PartName=""/xl/workbook.xml"" ContentType=""application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml""/>
|<Override PartName=""/xl/worksheets/sheet1.xml"" ContentType=""application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml""/>
|<Override PartName=""/xl/styles.xml"" ContentType=""application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml""/>
|</Types>";
ЗаписатьТекстовыйФайл(КаталогXLSX + "[Content_Types].xml", Содержимое);
Содержимое =
"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
|<Relationships xmlns=""http://schemas.openxmlformats.org/package/2006/relationships"">
|<Relationship Id=""rId1"" Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"" Target=""xl/workbook.xml""/>
|</Relationships>";
ЗаписатьТекстовыйФайл(КаталогXLSX + "_rels\.rels", Содержимое);
Содержимое =
"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
|<workbook xmlns=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"" xmlns:r=""http://schemas.openxmlformats.org/officeDocument/2006/relationships"">
|<bookViews><workbookView/></bookViews>
|<sheets><sheet name=""Контрагенты"" sheetId=""1"" r:id=""rId1""/></sheets>
|</workbook>";
ЗаписатьТекстовыйФайл(КаталогXLSX + "xl\workbook.xml", Содержимое);
Содержимое =
"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
|<Relationships xmlns=""http://schemas.openxmlformats.org/package/2006/relationships"">
|<Relationship Id=""rId1"" Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"" Target=""worksheets/sheet1.xml""/>
|<Relationship Id=""rId2"" Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"" Target=""styles.xml""/>
|</Relationships>";
ЗаписатьТекстовыйФайл(КаталогXLSX + "xl\_rels\workbook.xml.rels", Содержимое);
Содержимое =
"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
|<styleSheet xmlns=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"">
|<numFmts count=""1""><numFmt numFmtId=""164"" formatCode=""dd.mm.yyyy hh:mm:ss""/></numFmts>
|<fonts count=""2"">
|<font><sz val=""11""/><name val=""Calibri""/><family val=""2""/></font>
|<font><b/><sz val=""11""/><color rgb=""FF1F2937""/><name val=""Calibri""/><family val=""2""/></font>
|</fonts>
|<fills count=""3"">
|<fill><patternFill patternType=""none""/></fill>
|<fill><patternFill patternType=""gray125""/></fill>
|<fill><patternFill patternType=""solid""><fgColor rgb=""FFD9E1F2""/><bgColor indexed=""64""/></patternFill></fill>
|</fills>
|<borders count=""2"">
|<border><left/><right/><top/><bottom/><diagonal/></border>
|<border><left style=""thin""><color rgb=""FFD9D9D9""/></left><right style=""thin""><color rgb=""FFD9D9D9""/></right><top style=""thin""><color rgb=""FFD9D9D9""/></top><bottom style=""thin""><color rgb=""FFD9D9D9""/></bottom><diagonal/></border>
|</borders>
|<cellStyleXfs count=""1""><xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""0""/></cellStyleXfs>
|<cellXfs count=""5"">
|<xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0""/>
|<xf numFmtId=""0"" fontId=""1"" fillId=""2"" borderId=""1"" xfId=""0"" applyFont=""1"" applyFill=""1"" applyBorder=""1"" applyAlignment=""1""><alignment horizontal=""center"" vertical=""center"" wrapText=""1""/></xf>
|<xf numFmtId=""1"" fontId=""0"" fillId=""0"" borderId=""1"" xfId=""0"" applyNumberFormat=""1"" applyBorder=""1""/>
|<xf numFmtId=""164"" fontId=""0"" fillId=""0"" borderId=""1"" xfId=""0"" applyNumberFormat=""1"" applyBorder=""1""/>
|<xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""1"" xfId=""0"" applyBorder=""1""/>
|</cellXfs>
|<cellStyles count=""1""><cellStyle name=""Обычный"" xfId=""0"" builtinId=""0""/></cellStyles>
|</styleSheet>";
ЗаписатьТекстовыйФайл(КаталогXLSX + "xl\styles.xml", Содержимое);
КонецПроцедуры
Функция ЗаписатьЛистXLSX(ИмяФайла, Выборка)
Запись = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8);
Запись.Записать(
"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>"
+ "<worksheet xmlns=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"">"
+ "<sheetViews><sheetView workbookViewId=""0"" showGridLines=""0"">"
+ "<pane ySplit=""1"" topLeftCell=""A2"" activePane=""bottomLeft"" state=""frozen""/>"
+ "</sheetView></sheetViews>"
+ "<sheetFormatPr defaultRowHeight=""15""/>"
+ "<cols>"
+ "<col min=""1"" max=""1"" width=""32"" customWidth=""1""/>"
+ "<col min=""2"" max=""2"" width=""55"" customWidth=""1""/>"
+ "<col min=""3"" max=""3"" width=""18"" customWidth=""1""/>"
+ "<col min=""4"" max=""4"" width=""14"" customWidth=""1""/>"
+ "<col min=""5"" max=""6"" width=""24"" customWidth=""1""/>"
+ "</cols><sheetData>");
Запись.Записать(
"<row r=""1"" ht=""30"" customHeight=""1"">"
+ ЯчейкаТекст("A1", "Краткое наименование", 1)
+ ЯчейкаТекст("B1", "Полное наименование", 1)
+ ЯчейкаТекст("C1", "ИНН", 1)
+ ЯчейкаТекст("D1", "КПП", 1)
+ ЯчейкаТекст("E1", "Дата регистрации в MDM", 1)
+ ЯчейкаТекст("F1", "Дата изменения в MDM", 1)
+ "</row>");
Количество = 0;
НомерСтроки = 2;
Пока Выборка.Следующий() Цикл
СтрокаЛиста =
"<row r=""" + Строка(НомерСтроки) + """>"
+ ЯчейкаТекст("A" + Строка(НомерСтроки), Выборка.Наименование)
+ ЯчейкаТекст("B" + Строка(НомерСтроки), Выборка.ПолноеНаименование)
+ ЯчейкаИдентификатор("C" + Строка(НомерСтроки), Выборка.ИНН)
+ ЯчейкаИдентификатор("D" + Строка(НомерСтроки), Выборка.КПП)
+ ЯчейкаДата("E" + Строка(НомерСтроки), Выборка.ДатаСоздания)
+ ЯчейкаДата("F" + Строка(НомерСтроки), Выборка.ДатаИзменения)
+ "</row>";
Запись.Записать(СтрокаЛиста);
Количество = Количество + 1;
НомерСтроки = НоmerСтроки + 1; // Исправлена опечатка в имени переменной НомерСтроки
КонецЦикла;
ПоследняяСтрока = Макс(1, НомерСтроки - 1);
Запись.Записать(
"</sheetData><autoFilter ref=""A1:F"
+ Строка(ПоследняяСтрока)
+ """/><pageMargins left=""0.7"" right=""0.7"" top=""0.75"" bottom=""0.75"" header=""0.3"" footer=""0.3""/>"
+ "</worksheet>");
Запись.Закрыть();
Возврат Количество;
КонецФункции
Функция ЯчейкаТекст(Адрес, Значение, Стиль = 4)
АтрибутСтиля = ?(Стиль = 0, "", " s=""" + Строка(Стиль) + """");
Возврат "<c r=""" + Адрес + """" + АтрибутСтиля
+ " t=""inlineStr""><is><t xml:space=""preserve"">"
+ ЭкранироватьXML(Значение)
+ "</t></is></c>";
КонецФункции
Функция ЯчейкаИдентификатор(Адрес, Значение)
ТекстИдентификатора = СокрЛП(Строка(Значение));
Если СтрокаСодержитТолькоЦифры(ТекстИдентификатора) Тогда
Возврат "<c r=""" + Адрес + """ s=""2""><v>"
+ XMLСтрока(Число(ТекстИдентификатора))
+ "</v></c>";
КонецЕсли;
Возврат ЯчейкаТекст(Адрес, ТекстИдентификатора);
КонецФункции
Функция ЯчейкаДата(Адрес, Значение)
Если Не ЗначениеЗаполнено(Значение) Тогда
Возврат ЯчейкаТекст(Адрес, "");
КонецЕсли;
СерийныйНомерExcel = (Значение - Дата(1899, 12, 30)) / 86400;
Возврат "<c r=""" + Адрес + """ s=""3""><v>"
+ XMLСтрока(СерийныйНомерExcel)
+ "</v></c>";
</xf></numFmts>КонецФункции
Функция ЭкранироватьXML(Значение)
Результат = Строка(Значение);
Результат = СтрЗаменить(Результат, Символы.ВК, " ");
Результат = СтрЗаменить(Результат, Символы.ПС, " ");
Результат = СтрЗаменить(Результат, "&", "&");
Результат = СтрЗаменить(Результат, "<", "<");
Результат = СтрЗаменить(Результат, ">", ">");
Возврат Результат;
КонецФункции
Процедура ЗаписатьТекстовыйФайл(ИмяФайла, Содержимое)
Запись = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8);
Запись.Записать(Содержимое);
Запись.Закрыть();
КонецПроцедуры
Процедура УдалитьВременныеФайлыXLSX(КаталогXLSX, ИмяАрхива)
Пути = Новый Массив;
Пути.Добавить(КаталогXLSX + "[Content_Types].xml");
Пути.Добавить(КаталогXLSX + "_rels\.rels");
Пути.Добавить(КаталогXLSX + "xl\workbook.xml");
Пути.Добавить(КаталогXLSX + "xl\styles.xml");
Пути.Добавить(КаталогXLSX + "xl\_rels\workbook.xml.rels");
Пути.Добавить(КаталогXLSX + "xl\worksheets\sheet1.xml");
Пути.Добавить(ИмяАрхива);
Для Каждого Путь Из Пути Цикл
Попытка
УдалитьФайлы(Путь);
Исключение
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Функция СтрокаСодержитТолькоЦифры(ЗначениеПоля)
Если ПустаяСтрока(ЗначениеПоля) Тогда
Возврат Ложь;
КонецЕсли;
Для Позиция = 1 По СтрДлина(ЗначениеПоля) Цикл
Если СтрНайти("0123456789", Сред(ЗначениеПоля, Позиция, 1)) = 0 Тогда
Возврат Ложь;
КонецЕсли;
КонецЦикла;
Возврат Истина;
КонецФункции
#КонецОбласти
```
---
### Шаг 2. Создай объект метаданных в конфигураторе
В ветке **Общие -> Регламентные задания** добавь новое задание:
* **Имя:** `ЦФ_ВыгрузкаКонтрагентовВXLSX`
* **Имя метода:** `ЦФ_РегламентныеЗадания.ВыгрузитьКонтрагентовВXLSX` (выбираем процедуру из Шага 1).
* **Использование:** `Истина`
* **Предопределенное:** `Истина`
---
### Шаг 3. Зарегистрируй в БСП
Чтобы подсистема БСП увидела твое регламентное задание, зайди в общий модуль `РегламентныеЗаданияПереопределяемый` и добавь его в процедуру `ПриОпределенииНастроекРегламентныхЗаданий`:
```bsl
Процедура ПриОпределенииНастроекРегламентныхЗаданий(Настройки) Экспорт
Настройка = Настройки.Добавить();
Настройка.РегламентноеЗадание = Метаданные.РегламентныеЗадания.ЦФ_ВыгрузкаКонтрагентовВXLSX;
КонецПроцедуры
```
После обновления конфигурации базы данных задание появится в стандартной обработке БСП **«Регламентные и фоновые задания»**, где ты (или системный администратор) сможете настроить ему любое удобное расписание. Все ошибки или отчеты об успешной выгрузке будут падать прямо туда (через системный Журнал Регистрации).