1C: фоновая загрузка большого файла Excel в табличный документ

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

Решение: если поддержка фоновой работы в 1С была уже довольно давно, то асинхронная загрузка файлов на сервер появилась лишь начиная с версии 8.3.15.1489 ,Ну тоже уже давно, но руки добрались начать использовать только сейчас, т.к. ранее было не критично — не настолько большие файлы загружал/обрабатывал.

Итак, сначала на форме разместим индикатор загрузки. Для этого в реквизитах формы необходимо создать переменную типа «число», и перетащив её на форму выбрать тип «индикатор»:

индикатор загрузки
индикатор загрузки
индикатор загрузки

На кнопку «Загрузить ЛС» навесим открытие диалогового окна:

	Фильтр = "Файл с лицевыми счетами(*.xlsx)|*.xlsx";
	ПараметрыДиалога = новый ПараметрыДиалогаПомещенияФайлов("Выберите файлы XLSX", Истина, Фильтр);		

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

&НаКлиенте
Процедура ЗагрузитьЛС(Команда)
	этаформа.ИндикаторЗагрузкиЛС=0;				
	Фильтр = "Файл с лицевыми счетами(*.xlsx)|*.xlsx";
	ПараметрыДиалога = новый ПараметрыДиалогаПомещенияФайлов("Выберите файлы XLSX", Истина, Фильтр);		
	
	ОповещениеОЗавершении = новый ОписаниеОповещения("ПослеЗагрузкиФайлаЛС", ЭтаФорма);		
	ОповещениеОХодеЗагрузки = новый ОписаниеОповещения("ОповещениеОХодеЗагрузкиЛС", ЭтаФорма);		
	
	Этаформа.Элементы.ПояснениеКЗагрузкеЛС.Заголовок="Перемещаю файл на сервер..";
	НачатьПомещениеФайлаНаСервер(ОповещениеОЗавершении,ОповещениеОХодеЗагрузки,,,ПараметрыДиалога, УникальныйИдентификатор);								                                                 

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

&НаКлиенте
Процедура ПослеЗагрузкиФайлаЛС (ОписаниеФайла, ДопПараметры) Экспорт
	Этаформа.Элементы.ПояснениеКЗагрузкеЛС.Заголовок="Файл загружен, обрабатываю..";
	этаформа.ИндикаторЗагрузкиЛС=0;				
КонецПроцедуры	

&НаКлиенте
Процедура ОповещениеОХодеЗагрузкиЛС (ПомещаемыйФайл, Помещено, ОтказОтПомещенияФайла,ДополнительныеПараметры) Экспорт
	этаформа.ИндикаторЗагрузкиЛС=Помещено;				
КонецПроцедуры	

В результате чего после выбора файла, по экрану побежит индикатор хода перемещения файла на сервер. Далее этот файл необходимо будет обработать на сервере фоново. И тут возникает один нюанс: мы не можем передать в фоновое задание ссылку на перемещенный файл во временном хранилище.Точнее можем, но фоновое задание это хранилище прочитать не может (это то ли глюк, то ли фича платформы — не понятно). Проблема.. Тогда делаем финт ушами: перед уходом в «фон», мы создадим временный файл во временной папке пользователя 1С, и передадим в фон уже не ссылку на него, а непосредственно имя временного файла. Для этого я просто написал функцию, которая на входе получает адрес загруженного файла, а на выходе даёт имя временного файла:

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

Кроме того, чтобы мы могли передать результат в обработку по завершению фоновой работы, нам необходимо при запуске фонового задания передать в него некоторые параметры, а именно:

  • имя созданного временного файла
  • колонки/строки откуда брать данные из эксель файла
  • адрес временного хранилища, куда поместить результат работы фонового задания
&НаКлиенте
Процедура ПослеЗагрузкиФайлаЛС (ОписаниеФайла, ДопПараметры) Экспорт
	Этаформа.Элементы.ПояснениеКЗагрузкеЛС.Заголовок="Файл загружен, обрабатываю..";
	этаформа.ИндикаторЗагрузкиЛС=0;				
	инф=Новый Структура("ВременныйФайл,Расширение,ЛС_начало,ЛС_лс,ЛС_то,ЛС_дата_установки_пу,АдресВременногХранилища");
	инф.ВременныйФайл=СохранитьФайлНаСервере(ОписаниеФайла.адрес,ОписаниеФайла.ссылканафайл.расширение);
	инф.Расширение=ОписаниеФайла.ссылканафайл.расширение;
	инф.ЛС_начало=объект.ЛС_начало;
	инф.ЛС_лс=объект.ЛС_лс;
	инф.ЛС_то=объект.ЛС_то;
	инф.ЛС_дата_установки_пу=объект.ЛС_дата_установки_пу;
	инф.АдресВременногХранилища=объект.ВрХранилищеФормы;	
КонецПроцедуры	

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	объект.ВрХранилищеФормы=ПоместитьВоВременноеХранилище(0);
	ЗапуститьФЗЗагрузкиЛС(инф);	
ПодключитьОбработчикОжидания("ИндикаторВыполненияЗагрузкиФайловЛС",1,ложь);				
КонецПроцедуры

Функция которая будет работать фоново, должна размещаться в общем модуле. Это небольшой недостаток внешних обработок — фоново запускаются только процедуры-функции созданные внутри конфигурации. Но! до этого нам нужно опять же написать «обвязку» фонового задания, дабы мы имели возможность знать, работает или нет оно, а так-же на каком этапе. При запуске фонового задания, мы получаем идентификатор этого задания:

	Функция ЗапуститьФЗЗагрузкиЛС(Параметры)		
	МассивПараметров = Новый Массив;
	МассивПараметров.Добавить(Параметры);
	ФЗ = ФоновыеЗадания.Выполнить("СК_ГР_ДлительныеОперации.ЗагрузитьФайлыЛС",МассивПараметров);	
	объект.ЛС_ФЗ = ФЗ.УникальныйИдентификатор;					
КонецФункции  

Который в дальнейшем будем использоваться для того чтобы «узнать» как поживает собственно это задание, подключив на клиенте обработчик ожидания, выполняющийся раз в секунду:

&НаКлиенте
Процедура ИндикаторВыполненияЗагрузкиФайловЛС() Экспорт  
	пр=КакДелаУФЗЗагрузкиФайловЛС();   	
	если пр=неопределено тогда  
		этаформа.ИндикаторЗагрузкиЛС=100;
		ОтключитьОбработчикОжидания("ИндикаторВыполненияЗагрузкиФайловЛС");
		Этаформа.Элементы.ПояснениеКЗагрузкеЛС.Заголовок="Файлы обработаны";
	иначе
		этаформа.ИндикаторЗагрузкиЛС=пр;         		
	конецесли;
КонецПроцедуры	 
&НаСервере
Функция КакДелаУФЗЗагрузкиФайловЛС()
	ФЗ = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(объект.идФЗ);
	если ФЗ=Неопределено тогда		
		возврат неопределено;
	иначе	                                                                                                    
		если ФЗ.Состояние=СостояниеФоновогоЗадания.Завершено тогда
			возврат неопределено;
		конецесли;	   
		если ФЗ.Состояние=СостояниеФоновогоЗадания.ЗавершеноАварийно тогда
			Сообщить("Ошибка:"+ФЗ.ИнформацияОбОшибке.Описание);
			возврат неопределено;
		конецесли;										
	конецесли;	
	возврат 0;
конецфункции

Как видим, тут отслеживается состояние фонового задания (запущен, работает, завершен, завершен с ошибкой), но не отслеживается этап выполнения работ. Есть на самом деле два способа получения хода работы фонового задания:

  • перехват вывода функции Сообщить() на сервере, и парсинг данных из него. Например в фоновом задании можно с какой-то периодичностью выводить что-то вроде: Сообщить(«12.3%загружаю»);, а получив на клиенте эту запись показывать индикацию 12.3% и соответствующее пояснение.
  • Можно во время работы фонового задания ложить данные во временно хранилище, и читать их из клиента. НО! данный способ работает только в случай файловой БД. Циатата из справки 1С: «Данные, помещенные во временное хранилище в фоновом задании, не будут доступны из родительского сеанса до момента завершения фонового задания»

Посему остаётся таки отлавливать сообщения сервера по известному идентификатору фонового задания:

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

А в самом фоновом здании, городить огород с выводом сообщений:

// На входе:
//	парам.ВременныйФайл
//	парам.Расширение
//	парам.ЛС_начало
//	парам.ЛС_лс
//	парам.ЛС_то
//	парам.ЛС_дата_установки_пу
//	парам.АдресВременногХранилища
Функция ЗагрузитьФайлыЛСРасширенно(парам) экспорт    
	Сообщить("0%читаю файл на сервере");
	ТабличныйДокументХар = Новый ТабличныйДокумент; 
	ТабличныйДокументХар.Прочитать(парам.ВременныйФайл,СпособЧтенияЗначенийТабличногоДокумента.Значение);
	стар=0;
	Для Каждого ОбластьТД ИЗ ТабличныйДокументХар.Области Цикл         		     
	    ОбластьФайла = ТабличныйДокументХар.ПолучитьОбласть(ОбластьТД.Имя);
	    КолВоСтрокФайла = ОбластьФайла.ПолучитьРазмерОбластиДанныхПоВертикали();
	    КолВоКолонокФайла = ОбластьФайла.ПолучитьРазмерОбластиДанныхПоГоризонтали();

		НачСтрока=парам.ЛС_начало;КонСтрока=0;
	    НачСтрока = ?(НачСтрока = 0, 2, НачСтрока);
	    КонСтрока = ?(КонСтрока = 0, КолвоСтрокФайла, КонСтрока);
					
		//перебираем все строки без первой строки            
		Для нСтрокаТФ = НачСтрока ПО КонСтрока Цикл      				
			если стар<>Окр(нСтрокаТФ*100/КонСтрока) тогда
				Сообщить(Строка(Окр(нСтрокаТФ*100/КонСтрока))+"%обрабатываю файл");
				стар=Окр(нСтрокаТФ*100/КонСтрока);
			конецесли;				
		конеццикла;		
	конеццикла						
конецфункции	

Посему модифицируем и обработчик ожидания, добавив парсинг сообщений сервера:

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

Флибсута всё..

К сожалению пришла новость, что создатель сайта филибусты из-за тяжелой болезни скоро покинет наш мир. А вместе с ним и его детище. Очень жаль. Еще одна эпоха ушла, читал книги скачанные оттуда последние 10+ лет

HH и отзывы о работодателе

Предложило мне тут приложение HH оставить отзывы о работодателях, у которых пришлось в своё время поработать. Попытался оставить. Как итог: положительные отзывы публикуются моментально. Отрицательные — вот уже пару недель пытаюсь опубликовать, и все не проходят модерацию. Но я упорный. всё время меняю формулировки.. надеюсь кому то из модераторов надоест уже это и нажмут кнопку «пропустить».

P.S. Ну собственно это и ожидаемо, полистал несколько «работодателей» — плохих отзывов нет. Хотя точно знаю, что компания шлак.

1С: Хранение информации во внешних источниках данных

Как то упустил эту возможность платформы 1С и никогда ранее не использовал. А тут вдруг прилетела задача поработать файлами большого размера (xlsx, csv), на основании данных в которых нужно строить различного вида отчёты. Пришла мысль: а почему бы не загрузить данные этих файлов в БД, а затем уже спокойно стоить на основании этих данных отчёты. Какие-то регистры, справочники и т.п. создавать в конфигурации для этого посчитал излишним. Самое правильное, подумал я, хранить всё вообще в какой-то сторонней базе (в моём случае в PostgreSQL). А 1С использовать только для вывода и формирования отчётов. Вот тут то я и вспомнил, что когда то мельком читал про «внешние источники данных» в 1С. Значит пришло время попробовать хранение информации во внешних источниках данных.

Сначала создам БД, с таблицами ls и shemes со структурой вида::

хранение информации во внешних источниках данных.

, где id — автоинкримент типа integer. Забегая вперед, следует отметить, что 1С не умеет при добавлении данных во внешние источники данных, передавать инкрементальное значение, а потому создадим триггер, который будет выполнятся перед транзакцией запроса INSERT, и заменяет значение id, на следующее по порядку:

хранение информации во внешних источниках данных.

Подготовка закончена. Теперь подготовим платформу 1С. А именно необходимо установить драйвер ODBC. Под Linux ставим из репозитария, под Windows скачиваем и устанавливаем отсюда: https://www.postgresql.org/ftp/odbc/releases/REL-16_00_0005-mimalloc/

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

хранение информации во внешних источниках данных.

После чего, будут доступны для добавления в конфигуратор таблицы БД:

хранение информации во внешних источниках данных.

После того как нажмем «Готово», в конфигураторе будет что-то вроде:

Далее остаётся научится читать,писать и удалять эти данные:

// добвление
	новыелс=внешниеисточникиданных.ОтчетыДля1С.Таблицы.reports_1c_public_ls.СоздатьОбъект();	
	новыелс.name="4232344";
	новыелс.Записать();
// изменение 
	мХарактеристика = внешниеисточникиданных.ОтчетыДля1С.Таблицы.reports_1c_public_ls.НайтиПоПолю("id",3);
	мОбъект = мХарактеристика.ПолучитьОбъект();
	мОбъект.name = "Уря! Чебуршка родил слона!";
	мОбъект.Записать();

//удаление 
	мХарактеристика = внешниеисточникиданных.ОтчетыДля1С.Таблицы.reports_1c_public_ls.НайтиПоПолю("id",3);
	мОбъект = мХарактеристика.ПолучитьОбъект();
	мОбъект.Удалить();
// чтение
	
	Запрос = Новый Запрос;
	Запрос.Текст = 
		"ВЫБРАТЬ
		|	reports_1c_public_ls.id КАК id,
		|	reports_1c_public_ls.name КАК name,
		|	reports_1c_public_shemes.name КАК name1
		|ИЗ
		|	ВнешнийИсточникДанных.ОтчетыДля1С.Таблица.reports_1c_public_ls КАК reports_1c_public_ls
		|		ЛЕВОЕ СОЕДИНЕНИЕ ВнешнийИсточникДанных.ОтчетыДля1С.Таблица.reports_1c_public_shemes КАК reports_1c_public_shemes
		|		ПО reports_1c_public_ls.sheme = reports_1c_public_shemes.id";
	
	РезультатЗапроса = Запрос.Выполнить();
	
	ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
	
	Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
		сообщить(ВыборкаДетальныеЗаписи.name);
		сообщить(ВыборкаДетальныеЗаписи.name1);
	КонецЦикла;

Update 16/09/2025: таки автоувеличение идентификатора умеет. Достаточно его указать как «только чтение» в 1С:

Но можно и с триггерами мудрить для желающих 😉

Реле времени своими руками 2

Ранее уже делал подобную схему, но вышло слишком дорого и сложно. Поэтому реле времени своими руками буду упрощать и удешевлять 😉 Во первых уберем индикатор, соответственно сдвиговый регистр будет не нужен. Из индикации добавим светодиод, который будет показывать включено или выключено реле. Так-же подумав, решил добавить кнопку ручного включения/выключения реле. Сердцем будет микросхема attiny85. Так-же в схему добавил и блок питания (HLK-PM01), чтоб вся конструкция была единой платой

Принципиальная схема (потыкать в эмуляторе можно тут):

Реле времени своими руками

Схема на текстолите:

Реле времени своими руками
Реле времени своими руками

Ну и скетч:

bool myClock[]={1,0,1,0,0,0,0,0,0,0,0,0,0,0};
int myClock_len=sizeof(myClock);
int delayTime=6000;
bool button_active=false;
int inc=0;
uint32_t myTimer1;
int relay_pin = 4;

void setup() {
  pinMode(0, OUTPUT); 
  pinMode(2, INPUT_PULLUP);
  digitalWrite(0, HIGH); // Включение светодиода
  pinMode(relay_pin,OUTPUT);
}

void loop() {
  
    if (millis()-myTimer1>=delayTime||millis()-myTimer1<0){
        myTimer1=millis();
        if (button_active==false){
            if (myClock[inc]==0){
              digitalWrite(0, LOW);  // Выключение светодиода              
              digitalWrite(relay_pin,LOW);
            } else {
              digitalWrite(0, HIGH); // Включение светодиода
              digitalWrite(relay_pin,HIGH);
            };
            inc++;
            if (inc>myClock_len){
              inc=0;
            };
        };

    };

    if (digitalRead(2)==0){
      if (button_active==false){
        digitalWrite(0, HIGH); // Включение светодиода
        digitalWrite(relay_pin,HIGH);
        button_active=true;        
      } else {
          button_active=false;
          digitalWrite(0, LOW); // Выключение светодиода
          digitalWrite(relay_pin,LOW);
      };
      delay(1000);
    };


  
}

Вот так готовлю почву чем заниматься долгими зимними вечерами.. Итоговая стоимость предварительно, вышла примерно такая:

  • блок питания — 160р
  • attiny85 — 80р
  • текстолит — 150р
  • светодиод — 7р
  • реле — 150р

Итого — примерно 600р. Ну собственно это цена реле времени своими руками уже готового если покупать на валдберис. Но тут то своими руками 😉

1 2 3 4 5 6 71