Загрузка и обработка большого файла EXCEL в 1С с прогрессбаром

Задача: «фоново» загрузить файл эксель большого размера, с показом прогресса загрузки.

Решение:

В ходе выполнения задачи столкнулся с глюком платформы 1С, что при запуске фонового задания с параметром адреса временного хранилища файла, он приходит на сервер пустым. Потому пришлось делать «финт ушами», а именно перед запуском фонового задания, файл загрузить на сервер, получить его временное имя и уже затем передать его как параметр при запуске фонового задания.

Кроме того выплыла проблема с не рабочим способом передачи данных о загрузке при помощи хранения данных во «ВременныхХранилиах», описанным тут. Потому для получения прогресса воспользуемся возможностью зная идентификатор фонового процесса периодически получить с сервера данные выводимые при помощи «Сообщить()».

В общих модулях разместим следующий код фонового процесса:

&НаСервере
Функция ЗагрузкаФайлаПредварительная(АдресВременногоХранилищаФайла) Экспорт			
	ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресВременногоХранилищаФайла);
	ИмяВременногоФайлаСпр = ПолучитьИмяВременногоФайла("xlsx");
	ДвоичныеДанные.Записать(ИмяВременногоФайлаСпр);     
	возврат ИмяВременногоФайлаСпр;	
конецфункции	

Процедура ЗагрузкаФайлаДлительная(ИмяВременногоФайлаСпр) Экспорт			
		
    ТабличныйДокументСпр = Новый ТабличныйДокумент;
    ТабличныйДокументСпр.Прочитать(ИмяВременногоФайлаСпр,СпособЧтенияЗначенийТабличногоДокумента.Значение);
	
	//читаем все листы файла СправочникСМС
	Для Каждого ОбластьТД ИЗ ТабличныйДокументСпр.Области Цикл         		     
            ОбластьФайла = ТабличныйДокументСпр.ПолучитьОбласть(ОбластьТД.Имя);
            КолВоСтрокФайла = ОбластьФайла.ПолучитьРазмерОбластиДанныхПоВертикали();
            КолВоКолонокФайла = ОбластьФайла.ПолучитьРазмерОбластиДанныхПоГоризонтали();
            Сообщить("Строк:"+КолВоСтрокФайла);
            Сообщить("Колонок:"+КолВоКолонокФайла);         
            имялиста=ОбластьТД.Имя;
            Сообщить("Лист:"+имялиста);         
                    МассивИменКолонок=Новый Массив();
                    // читаем шапку листа
                    Для ит=1 ПО КолВоКолонокФайла Цикл
                        нКолонка = СтрЗаменить(ит, Символы.НПП, "");
                        ИмяКолонки=ОбластьФайла.ПолучитьОбласть("R3" + "C"+нКолонка).ТекущаяОбласть.Текст;                        
                        МассивИменКолонок.Добавить(ИмяКолонки);
					конеццикла; 					
					НачСтрока=3;КонСтрока=0;
                    НачСтрока = ?(НачСтрока = 0, 2, НачСтрока);
                    КонСтрока = ?(КонСтрока = 0, КолвоСтрокФайла, КонСтрока);
                    //перебираем все строки без первой строки  
					Индикатор=0;
                    Для нСтрокаТФ = НачСтрока+1 ПО КонСтрока Цикл
                        нСтрока = СтрЗаменить(нСтрокаТФ, Символы.НПП, "");                        
			если Окр(нСтрокаТФ*100/КонСтрока)<>Индикатор тогда
				Индикатор = Окр(нСтрокаТФ*100/КонСтрока);
				Сообщение = Новый СообщениеПользователю;
				Сообщение.Текст = "Выполнено %"+Индикатор;
				Сообщение.Сообщить();
			конецесли;
			Для нКолонкаТФ = 1 ПО КолВоКолонокФайла Цикл
                            нКолонка = СтрЗаменить(нКолонкаТФ, Символы.НПП, "");
                            Область = ОбластьФайла.ПолучитьОбласть("R"+нСтрока+"C"+нКолонка);
                            ТекущаяОбласть = Область.ТекущаяОбласть;
                            ЗначениеЯчейки = СокрЛП(ТекущаяОбласть.Текст);
			    ...
				обрабатываем файл
			    ...	
                        конеццикла;                         		
	конеццикла;
	Сообщение = Новый СообщениеПользователю;
	Сообщение.Текст = "Загрузка завершена.";
	Сообщение.Сообщить();

Клиентская часть в управляемых формах:

&НаКлиенте
Процедура Жмяк(Команда)
Режим = РежимДиалогаВыбораФайла.Открытие;
    ДиалогОткрытияФайла = Новый ДиалогВыбораФайла(Режим);
    ДиалогОткрытияФайла.ПолноеИмяФайла = "";
    Фильтр = НСтр("ru = 'Текст'; en = 'Text'")+ "(*.xls)|*.xlsx";
    ДиалогОткрытияФайла.Фильтр = Фильтр;
    ДиалогОткрытияФайла.МножественныйВыбор = ложь;
    ДиалогОткрытияФайла.Заголовок = "Выберите файлы";
    Если ДиалогОткрытияФайла.Выбрать() Тогда
        МассивФайлов = ДиалогОткрытияФайла.ВыбранныеФайлы;
		ФайлСМС="";
		Для Каждого ИмяФайла Из МассивФайлов Цикл
			ФайлСМС=ИмяФайла;
		конеццикла;
		если ФайлСМС<>"" тогда 			
					АдресХранилищаФайла = "";			
					Состояние("Перемещаю файл на сервер");
		            ПоместитьФайл(АдресХранилищаФайла, ФайлСМС, , Ложь, ЭтаФорма.УникальныйИдентификатор);   					
					Состояние("Обрабатывается файл "+ФайлСМС);							
					ЗапускФоновойЗагрузкиСправочника(АдресХранилищаФайла);					
					Состояние("Запущена фоновая обработка файла");
					ПодключитьОбработчикОжидания("ИндикаторВыполненияЗагрузки",1,ложь);	
		иначе
			сообщить("Файл не выбран");
		конецесли;	
	конецесли;	        

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


&НаСервере
Процедура ЗапускФоновойЗагрузкиСправочника(АдресХранилищаФайла)	
	ИмяВременногоФайлаСпр=СК_Регламентные.ЗагрузкаФайлаПредварительная(АдресХранилищаФайла);	
	МассивПараметров = Новый Массив;
	МассивПараметров.Добавить(ИмяВременногоФайлаСпр);		
	Сообщить("Адрес файла (1):"+АдресХранилищаФайла);									
	ФЗ = ФоновыеЗадания.Выполнить("СК_Регламентные.ЗагрузкаФайлаДлительная",МассивПараметров);	
	ЭтаФорма.ФоновоеИдентификатор = ФЗ.УникальныйИдентификатор;
	
							
КонецПроцедуры
&НаСервере
Функция ПолучитьСообщенияФЗ(ФЗ, Состояние = Неопределено, УдалятьСообщения = Ложь) Экспорт
	Если Состояние = Неопределено Тогда
		Состояние = ФЗ.Состояние;
	КонецЕсли;
	МассивСообщений = Новый Массив;
	Сообщения = ФЗ.ПолучитьСообщенияПользователю(УдалятьСообщения);
	Если Сообщения <> Неопределено Тогда
		Для Каждого Сообщение Из Сообщения Цикл
			МассивСообщений.Добавить(Сообщение.Текст);
		КонецЦикла;
	КонецЕсли;
	Возврат МассивСообщений;
КонецФункции

&НаСервере
Функция ОпроситьФоновые()
	прог=неопределено;
	ФЗ = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(ЭтаФорма.ФоновоеИдентификатор);
	ФСообщения=ПолучитьСообщенияФЗ(ФЗ,,истина);
	Если ФСообщения.Количество() > 0 Тогда
		Для Каждого Сообщение Из ФСообщения Цикл
			Сообщить(Сообщение);
			если найти(Сообщение,"%")>0 тогда
				Этаформа.Прогресс=СтрЗаменить(Сообщение,"Выполнено %","");			
				прог=Этаформа.Прогресс;
			конецесли;
		КонецЦикла;
	КонецЕсли;
	возврат прог;
КонецФункции
&НаКлиенте
Процедура ИндикаторВыполненияЗагрузки() Экспорт
	пр=ОпроситьФоновые();
	если пр<>неопределено тогда
		Состояние("Выполнено "+пр);
	конецесли;
КонецПроцедуры

1C: Еще один вариант вывода данных с пагинацией

На этот раз, данные подготовлены для JSON JqGrid. Рабочий вариант для не большого количества записей (около 1000). В противном случае будут «тормоза» в 1С, т.к. листается вся выборка для построения «страницы» (не моё).

Функция GetListing(body, answer) Экспорт	
	answer.result = Новый Массив;
	answer.error = Истина;
	answer.errortext = "Ошибка!";		
		Если body.Свойство("page") Тогда
			page = Число(body.page);
		Иначе
			page = 1;
		КонецЕсли;
		
		Если body.Свойство("limit") Тогда
			limit = Число(body.limit);
		Иначе
			limit = 20;
		КонецЕсли;
		
		Если body.Свойство("sidx") Тогда
			sidx = body.sidx;
		Иначе
			sidx = "";
		КонецЕсли;
		
		Если body.Свойство("sord") Тогда
			sord = body.sord;
		Иначе
			sord = "";
		КонецЕсли;
		
		Если body.Свойство("flt") Тогда
			flt = body.flt;
		Иначе
			flt = Неопределено;
		КонецЕсли;			
			
	Запрос = Новый Запрос;
	Запрос.Текст = 
		"ВЫБРАТЬ
		|	ХрюХрю.Номер КАК id,
		|	ХрюХрю.НачалоЧесания КАК dtstart,
		|	ХрюХрю.ДлительностьПочесывания КАК long,
		|	ХрюХрю.Пятачок.Наименование КАК column,
		|	ХрюХрю.ВыходПятачка.OCPP_num КАК outlet,
		|	ХрюХрю.НачальныеПоказанияСчастья КАК meterstart,
		|	ХрюХрю.КонечныеПоказанияСчастья КАК meterend,
		|	ПРЕДСТАВЛЕНИЕ(ХрюХрю.ТекущийСтатусПочесывания) КАК status,
		|	ХрюХрю.ТекущийСтатусПочесывания КАК ТекущийСтатусПочесывания
		|ПОМЕСТИТЬ тЗарядки
		|ИЗ
		|	Документ.ХрюХрю КАК ХрюХрю
		|ГДЕ
		|	ХрюХрю.ЭЗС.Владелец.Код = &Код
		|	И ХрюХрю.ПометкаУдаления = ЛОЖЬ
		|	И ХрюХрю.НачалоЧесания МЕЖДУ &ДатаНачала И &ДатаКонца
		|;
		|
		|////////////////////////////////////////////////////////////////////////////////
		|ВЫБРАТЬ
		|	тЗарядки.column КАК column,
		|	тЗарядки.dtstart КАК dtstart,
		|	тЗарядки.id КАК id,
		|	тЗарядки.long КАК long,
		|	тЗарядки.meterend КАК meterend,
		|	тЗарядки.meterstart КАК meterstart,
		|	тЗарядки.outlet КАК outlet,
		|	тЗарядки.status КАК status,
		|	тЗарядки.ТекущийСтатусПочесывания КАК ТекущийСтатусПочесывания
		|ИЗ
		|	тЗарядки КАК тЗарядки
		|	&Фильтры
		|	&Сортировка	";
	Запрос.УстановитьПараметр("Код", пп.Владелец.Код);
	Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(Дата(body.datefrom)));
	Запрос.УстановитьПараметр("ДатаКонца", КонецДня(Дата(body.datefrom)));		
	
		Сортировка = "";
		Если ЗначениеЗаполнено(sidx) Тогда
				Сортировка = "УПОРЯДОЧИТЬ ПО " + sidx + " " + sord + ", id desc";
		КонецЕсли;
		Запрос.Текст = СтрЗаменить(Запрос.Текст, "&Сортировка", Сортировка);
		
		Условие = "";
		status="";
		Если ЗначениеЗаполнено(flt) Тогда
			groupOp = flt.groupOp;
			rules = flt.rules;
			Для Каждого rule Из rules Цикл			
				Если rule.op = "bw" Тогда
					ЧастичноеУсловие = "(" + rule.field + " ПОДОБНО ""%" + rule.data + "%"")";
					Условие = Условие + ?(ЗначениеЗаполнено(Условие), " " + groupOp + " ", "") + ЧастичноеУсловие;					
				конецесли;
				Если rule.op = "eq" Тогда
					если rule.data<>"-1" тогда
						Если rule.field = "status" Тогда
							ЧастичноеУсловие = "(ТекущийСтатусПочесывания = &status)";
							status=rule.data;
						иначе
							ЧастичноеУсловие = "(" + rule.field + " = """ + rule.data + """)";
						конецесли;							
						Условие = Условие + ?(ЗначениеЗаполнено(Условие), " " + groupOp + " ", "") + ЧастичноеУсловие;
					конецесли;
				конецесли;
				
			конеццикла;				
			Если ЗначениеЗаполнено(Условие) Тогда
				Условие = "ГДЕ " + Условие;
			КонецЕсли;
		конецесли;		
	
	Запрос.Текст = СтрЗаменить(Запрос.Текст, "&Фильтры", Условие);
	если status<>"" тогда
		status=перечисления.СтатусыПочесывания[status];
		Запрос.УстановитьПараметр("status", status);
	конецесли;			
	
	РезультатЗапроса = Запрос.Выполнить();	
	ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
		
	rows = Новый Массив();
	РезультатЗапроса = Запрос.Выполнить().Выгрузить(); // нехорошо

		колстрок = РезультатЗапроса.Количество();
		total = Цел((колстрок - 1) / limit) + 1;		
		rowid = (page - 1) * limit;
		lastrow = rowid + limit - 1;
		records=0;
		Пока rowid <= lastrow И rowid < колстрок Цикл
			Стр = РезультатЗапроса[rowid];
			cell = Новый Массив;
			cell.Добавить(Стр.id);
			cell.Добавить(Стр.dtstart);
			cell.Добавить(Стр.long);
			cell.Добавить(Стр.column);
			cell.Добавить(Стр.outlet);
			cell.Добавить(Стр.status);
			cell.Добавить(Стр.meterend-Стр.meterend);
			cell.Добавить(0);
			row = Новый Структура("id, cell", Стр.id, cell);
			rows.Добавить(row);
			rowid = rowid + 1;
			records=records+1;
		КонецЦикла;
		
		result = Новый Структура("page, total, records, rows", page, total, records, rows); 		
		
	answer.result = result;	
	answer.errortext="";
	answer.error = ложь;
	
	возврат answer;
конецфункции	

1С: Не выполняются регламентные задания

У платформы 1С в регламентных заданиях есть одна фишечка, на которую уже не первый раз попадаюсь. А именно, при создании регламентного задания в конфигураторе, его расписание в приложение «улетает» только один раз. А далее, это расписание хоть завыставляйся  в конфигураторе — выполняться оно будет по расписанию выставленному в приложении.

Следовательно алгоритм следующий: создали регламентное задание , задали расписание. Если нужно изменить расписание — идем в приложение и выставляем его там, в «Обработка.РегламентныеИФоновыеЗадания»




1С: Пять способов вывода сообщений с «сервера»

Конфигурации, платформы и БСП разные бывают, потому где-то сработает одно, где-то другое.

1) Универсальный способ: 

2)  Через СообщениеПользователю:

3) Старые БСП (ниже 3-й версии):

4) Свежие БСП (выше 3 версии)

5) Через «ВызватьИсключение»

Может быть полезно например при отказе в проведении документа в процедуре ОбработкаПроведения:




Как постранично (порционно) листать результат запроса в 1С (ака LIMIT x,y в MySQL)

Большая беда MSSQL и как следствие 1С — отсутствие возможности порционно листать записи результата запроса как в MySQL. В результате рождаются такие монстроузные алгоритмы как ниже (код не мой):

Если вкратце, то алгоритм следующий:

  1.  Делаем выборку ВСЕХ записей, определяя, с какой записи было начало предыдущей страницы
  2. Начиная с этой записи, делаем выборку N записей
  3. Передаем результат

Интересно будет посмотреть как этот алгоритм будет вести себя при миллионах записей..

 




1 19 20 21 22 23 51