Несколько статей уже на моём сайте на эту тему есть вывод отчета из макета), но они слегка протухли за прошедшее время, и почему-то отображаются не вполне корректно. Потому повторю.
Сначала нужно создать макет:
Нарисуем структуру отчета, присвоим имена блокам, обозначим ячейки которые будут заполнятся параметрами.
Далее на клиенте на форму выведем кнопку, и на неё повесим на неё обработчик заполнения отчета и вывода его на экран:
&НаКлиенте
Процедура СформироватьОтчёт(Команда)
ТабличныйДокумент = Новый ТабличныйДокумент;
ТабличныйДокумент=ПечатнаяФормаНаСервере();
ТабличныйДокумент.Показать();
КонецПроцедуры
Формирование и вывод отчета из макета:
&НаСервере
Функция ПечатнаяФормаНаСервере()
ТабДок=Новый ТабличныйДокумент;
Макет=РеквизитФормыВЗначение("Объект").ПолучитьМакет("МакетУдовлетворённость");
ОбластьШапка=Макет.ПолучитьОбласть("Шапка");
ТабДок.Вывести(ОбластьШапка);
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
ОбластьТушка=Макет.ПолучитьОбласть("тушка");
ОбластьТушка.Параметры.ид=ВыборкаДетальныеЗаписи.ИдентификаторОпроса;
ТабДок.Вывести(ОбластьТушка);
КонецЦикла;
...
возврат ТабДок;
КонецФункции
Пару лет назад уже сталкивался с подобной задачей (Фоновая обработка больших данных в 1С). Чуть погуглил, что изменилось за это время. А фактически ничего. До сих пор для того чтобы показать обычный прогресс бар, приходится использовать велосипеды. Временные хранилища, для того чтобы передать в клиента результат работы фоновой функции/процедуры как нельзя было использовать, так и сейчас нельзя. Прогресс бар, как нельзя было использовать без костылей…так и сейчас нельзя. Ну я конечно не смотрел что в БСП, т.к. зачастую приходится делать дописки где БСП или нет, или она древняя
Общая «шаблонная» схема использования фонового выполнения функций в 1С можно организаовать следующим образом:
Запускаем фоновое задание
На клиенте запускаем периодическое задание которое отлавливает вывод «сообщить» на сервере
В фоновом задании при помощи «сообщить» выводим всякую служебную информацию. Если нужно в «клиент» передать данные — записываем временный файл в формате например json, и имя его, опять же при помощи «Сообщить» передаём на клиент.
По окончании фонового задания, закрываем выполнение периодического задания
Процедура или функция фонового задания обязательно должна находиться в общем модуле:
Функция СпарситьИсходныеДанныеНаСервер(параметры) экспорт;
...
Сообщить("Выполнено 10%");
....
Сообщить("Выполнено 100%");
ИмяФайла=ПолучитьИмяВременногоФайла("json");
Текст = Новый ЗаписьТекста(ГдеИскать+ИмяФайла, КодировкаТекста.UTF8);
Текст.Записать(json_str);
Сообщить("Результат:"+ИмяФайла);
...
Периодическая проверка и «отлов» серверного вывода «Сообщить»:
&НаКлиенте
Процедура ИндикаторВыполненияЗагрузки() Экспорт
пр=ОпроситьФоновые();
если пр<>неопределено тогда
объект.ИндВыполнения=пр;
Состояние("Выполнено "+пр);
конецесли;
КонецПроцедуры
&НаКлиенте
Процедура ИндикаторВыполненияЗагрузки() Экспорт
пр=ОпроситьФоновые();
если пр<>неопределено тогда
объект.ИндВыполнения=пр;
Состояние("Выполнено "+пр);
конецесли;
КонецПроцедуры
&НаСервере
Функция ПолучитьСообщенияФЗ(ФЗ, Состояние = Неопределено, УдалятьСообщения = Ложь) Экспорт
Если Состояние = Неопределено Тогда
Состояние = ФЗ.Состояние;
КонецЕсли;
МассивСообщений = Новый Массив;
Сообщения = ФЗ.ПолучитьСообщенияПользователю(УдалятьСообщения);
Если Сообщения <> Неопределено Тогда
Для Каждого Сообщение Из Сообщения Цикл
МассивСообщений.Добавить(Сообщение.Текст);
КонецЦикла;
КонецЕсли;
Возврат МассивСообщений;
КонецФункции
&НаСервере
Функция ОпроситьФоновые()
прог=неопределено;
ФЗ = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(ЭтаФорма.ФоновоеИдентификатор);
если ФЗ=Неопределено тогда
ОтключитьОбработчикОжидания("ИндикаторВыполненияЗагрузки");
возврат ложь;
конецесли;
ФСообщения=ПолучитьСообщенияФЗ(ФЗ,,истина);
Если ФСообщения.Количество() > 0 Тогда
Для Каждого Сообщение Из ФСообщения Цикл
Сообщить(Сообщение);
если найти(Сообщение,"%")>0 тогда
прог=Число(СтрЗаменить(СтрЗаменить(Сообщение,"Выполнено ",""),"%",""));
конецесли;
если найти(Сообщение,"Результат:")>0 тогда
ИмяФайла=СтрЗаменить(Сообщение,"Результат:","");
Текст = Новый ЧтениеТекста(КаталогВременныхФайлов()+ИмяФайла, КодировкаТекста.UTF8);
СтрокаJson = JsonВСтруктуру(Текст.Прочитать());
ОтключитьОбработчикОжидания("ИндикаторВыполненияЗагрузки");
конецесли;
КонецЦикла;
КонецЕсли;
возврат прог;
КонецФункции
Задача: есть два массива структур. Один 500 записей, второй — порядка 900 тыс. Нужно для каждой из 500 записей, найти соответствующую запись из второй структуры.
Массив «субабоненты», заполнен структурами вида «то,лс,ипу,окпу,нс,улица,дом,квартира»
Массив «реестр_замен», заполнен структурами вида «лс,нп,улица,дом,квартира,ипу,дата_установки»
Решение 1: ищем и сопоставляем в «лоб»
найдено=0;
для каждого суб из ИсходныеДанные.субабоненты цикл
поз=поз+1;
для каждого стр из ИсходныеДанные.реестр_замен цикл
если суб.лс=стр.лс тогда
найдено=найдено+1;
конецесли;
конеццикла;
конеццикла;
сообщить("-сопоставлено лс: "+найдено);
Замеряем время выполнения…и устаём ждать.. Поиск и сопоставление длится по крайне мере несколько часов..
Решение 2:
А зачем нам в массиве субабонентов держать те данные, которых нет? Правильно, не зачем. Поэтому из массива «реестр_замен», сначала вычленим список л/с, и положим его в отдельный массив. И далее при заполнении из файла массива субабонентов, нужно штатно (функция Найти) проверять нужна такая строчка или нет в результирующем массиве? Функция «Найти» скажем работает ОЧЕНЬ быстро.
если ИсходныеДанные.список_лс.Найти(инф.лс)<>неопределено или ИсходныеДанные.список_ипу.Найти(инф.ипу)<>неопределено тогда
ИсходныеДанные.субабоненты.Добавить(инф);
конецесли;
В итоге, в массиве «субабоненты» у нас ровно то количество записей, которое в «реестр_замен «, а вовсе не 900тыс.
И дальше пробуем снова сопоставить:
найдено=0;
для каждого суб из ИсходныеДанные.субабоненты цикл
поз=поз+1;
для каждого стр из ИсходныеДанные.реестр_замен цикл
если суб.лс=стр.лс тогда
найдено=найдено+1;
конецесли;
конеццикла;
конеццикла;
сообщить("-сопоставлено лс: "+найдено);
Скрипт выполнился уже в приемлемые примерно 400 секунд
Куда ни глянь, в интернете чаще всего предлагается обработка и чтение большого файла csv в 1С при помощи загрузки в «текстовыйДокумент». Примерно так:
ТекстДок = Новый ТекстовыйДокумент; ТекстДок.Прочитать(ИмяВременногоФайлаХар); Для Индекс = 2 По ТекстДок.КоличествоСтрок() Цикл СтрокаФайла = ТекстДок.ПолучитьСтроку(Индекс); конеццикла;
Что в корне не верно, при обработке большого csv файла, так как в этом случае весь файл вычитывается предварительно в память. А она не безразмерная в большинстве случаев. Правильный же способ обработки — построчное чтение файла. Примерно так:
Текст = Новый ЧтениеТекста(ИмяВременногоФайлаХар, КодировкаТекста.ANSI);
Стр = Текст.ПрочитатьСтроку();
Пока Стр <> Неопределено Цикл
Сообщить(Стр);
Стр = Текст.ПрочитатьСтроку();
МассивПодстрок = СтрРазделить(Стр, ";");
инф=Новый Структура("то,лс,ипу,окпу,нс,улица,дом,квартира");
инф.то =СокрЛП(МассивПодстрок[2]);
инф.лс =СокрЛП(СтрЗаменить(МассивПодстрок[3],"-",""));
инф.ипу =СокрЛП(МассивПодстрок[31]);
инф.окпу =СокрЛП(МассивПодстрок[92]);
инф.нс =СокрЛП(МассивПодстрок[8]);
инф.улица =СокрЛП(МассивПодстрок[9]);
инф.дом =СокрЛП(МассивПодстрок[10]);
инф.квартира=СокрЛП(МассивПодстрок[11]);
ИсходныеДанные.субабоненты.Добавить(инф);
КонецЦикла;
Задача добавление рабочих дней к дате (добавить рабочие дни к дате) встречается довольно часто. И штатно должна решаться с помощью стандартного функционала БСП — «Производственный календарь». Например так:
Функция ДобавитьКДатеРабочиеДни(ДатаНач,ЧислоРабочихДней) экспорт
Запрос=Новый Запрос;
Запрос.Текст="ВЫБРАТЬ
| ВЫБОР
| КОГДА РегламентированныйПроизводственныйКалендарь.ВидДня = &РабочийДень
| ИЛИ РегламентированныйПроизводственныйКалендарь.ВидДня = &ПредпраздничныйДень
| ТОГДА 1
| ИНАЧЕ 0
| КОНЕЦ КАК ЧислоРабочихДней,
| РегламентированныйПроизводственныйКалендарь.ДатаКалендаря КАК ДатаКалендаря
|ПОМЕСТИТЬ ТЗ
|ИЗ
| РегистрСведений.РегламентированныйПроизводственныйКалендарь КАК РегламентированныйПроизводственныйКалендарь
|ГДЕ
| РегламентированныйПроизводственныйКалендарь.ДатаКалендаря МЕЖДУ &ДатаНач И &ДатаКон
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ТЗ.ДатаКалендаря,
| СУММА(ТЗ1.ЧислоРабочихДней) КАК ЧислоРабочихДней
|ПОМЕСТИТЬ ТЗНакопл
|ИЗ
| ТЗ КАК ТЗ
| ЛЕВОЕ СОЕДИНЕНИЕ ТЗ КАК ТЗ1
| ПО (ТЗ1.ДатаКалендаря <= ТЗ.ДатаКалендаря)
|
|СГРУППИРОВАТЬ ПО
| ТЗ.ДатаКалендаря
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| МАКСИМУМ(ТЗ.ДатаКалендаря) КАК ДатаКалендаря
|ИЗ
| ТЗНакопл КАК ТЗ
|ГДЕ
| ТЗ.ЧислоРабочихДней = &ЧислоРабочихДней";
Запрос.УстановитьПараметр("ДатаНач", ДатаНач);
ЧислоНедель = Цел(ЧислоРабочихДней/5+0.5);
ЧислоВыходныхДней = ЧислоНедель*2;
МаксимальноеКоличествоПраздничныхДнейПодряд = 8; //РождественскиеКаникулы
ДатаКон = ДатаНач+(ЧислоВыходныхДней+ЧислоРабочихДней+МаксимальноеКоличествоПраздничныхДнейПодряд)*24*60*60;
Запрос.УстановитьПараметр("ДатаКон",ДатаКон);
Запрос.УстановитьПараметр("ЧислоРабочихДней",ЧислоРабочихДней);
Запрос.УстановитьПараметр("РабочийДень", Перечисления.ВидыДнейПроизводственногоКалендаря.Рабочий);
Запрос.УстановитьПараметр("ПредпраздничныйДень", Перечисления.ВидыДнейПроизводственногоКалендаря.Предпраздничный);
Выборка = Запрос.Выполнить().Выбрать();
Результат = 0;
Если Выборка.Следующий() Тогда
Результат = КонецДня(Выборка.ДатаКалендаря)+1;
Конецесли;
Возврат Результат;
КонецФункции
Однако не всё же не все конфигурации содержат производственный календарь. Например почему-то обделено Делопроизводство 2.0. Что-бы не городить огород,, решил задачу «в лоб», без учёта праздничных дней — просто проверяю выпадение даты на субботу-воскресенье. В моём случае этого хватило.
Функция ДобавитьКДатеРабочиеДни(ДатаПроверки,дней)
добавлено=0;
пока добавлено<>дней цикл
ДатаПроверки=ДатаПроверки+86400;
Если ДеньНедели(ДатаПроверки) <> 6 ИЛИ ДеньНедели(ДатаПроверки) <> 7 Тогда
добавлено=добавлено+1;
конецесли;
конеццикла;
Возврат ДатаПроверки;
КонецФункции