Загрузка данных
/ Форма «Загрузка артикулов» внешнего отчета «Остатки товаров по артикулам».
//
// Открывается из основной формы отчета, возвращает владельцу Массив из Строка —
// введенные артикулы. При отказе возвращает Неопределено.
//
// Артикулы вводятся в поле табличного документа, а не в таблицу формы: только так
// работает вставка колонки из буфера обмена (Ctrl+V) — таблица управляемой формы
// многострочную вставку не принимает. Строка 1 — шапка «Артикулы», данные с 2-й.
// Так же устроены типовые формы загрузки из внешних файлов, например
// ОбщаяФорма.ЗагрузкаИдентификаторовИзВнешнихФайлов.
#Область ОбработчикиСобытийФормы
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
ИнициализироватьТабличныйДокумент();
КонецПроцедуры
#КонецОбласти
#Область ОбработчикиКомандФормы
&НаКлиенте
Процедура Готово(Команда)
СписокАртикулов = ВведенныеАртикулы();
Если СписокАртикулов.Количество() = 0 Тогда
ПоказатьПредупреждение(, НСтр("ru = 'Не введено ни одного артикула.'"));
Возврат;
КонецЕсли;
Закрыть(СписокАртикулов);
КонецПроцедуры
&НаКлиенте
Процедура ЗакрытьФорму(Команда)
Закрыть(Неопределено);
КонецПроцедуры
&НаКлиенте
Процедура ОчиститьСписок(Команда)
ИнициализироватьТабличныйДокумент();
КонецПроцедуры
&НаКлиенте
Процедура ЗаполнитьИзФайла(Команда)
Оповещение = Новый ОписаниеОповещения("ЗаполнитьИзФайлаЗавершение", ЭтотОбъект);
ПараметрыЗагрузки = ФайловаяСистемаКлиент.ПараметрыЗагрузкиФайла();
ПараметрыЗагрузки.ИдентификаторФормы = УникальныйИдентификатор;
ПараметрыЗагрузки.Диалог.Заголовок = НСтр("ru = 'Выберите файл с артикулами'");
ПараметрыЗагрузки.Диалог.Фильтр = НСтр(
"ru = 'Все поддерживаемые форматы (*.xlsx;*.xls;*.ods;*.mxl;*.csv;*.txt)|*.xlsx;*.xls;*.ods;*.mxl;*.csv;*.txt'");
ФайловаяСистемаКлиент.ЗагрузитьФайл(Оповещение, ПараметрыЗагрузки);
КонецПроцедуры
#КонецОбласти
#Область СлужебныеПроцедурыИФункции
// Обработчик выбора файла: дописывает артикулы из первой колонки файла в таблицу.
//
// Параметры:
// ПомещенныйФайл - Структура - см. ФайловаяСистемаКлиент.ЗагрузитьФайл
// - Неопределено - пользователь отказался от выбора.
// ДополнительныеПараметры - Произвольный - не используется.
//
&НаКлиенте
Процедура ЗаполнитьИзФайлаЗавершение(ПомещенныйФайл, ДополнительныеПараметры) Экспорт
Если ПомещенныйФайл = Неопределено Тогда
Возврат;
КонецЕсли;
ОписаниеОшибки = "";
Добавлено = ДобавитьАртикулыИзФайла(ПомещенныйФайл.Хранение, ПомещенныйФайл.Имя, ОписаниеОшибки);
Если Не ПустаяСтрока(ОписаниеОшибки) Тогда
ПоказатьПредупреждение(, ОписаниеОшибки);
ИначеЕсли Добавлено = 0 Тогда
ПоказатьПредупреждение(, НСтр("ru = 'В первой колонке файла нет ни одного значения.'"));
КонецЕсли;
КонецПроцедуры
// Готовит пустой бланк: шапка «Артикулы» в первой строке, ниже — место для ввода.
//
&НаСервере
Процедура ИнициализироватьТабличныйДокумент()
ТаблицаАртикулов.Очистить();
Линия = Новый Линия(ТипЛинииЯчейкиТабличногоДокумента.Сплошная);
Шапка = ТаблицаАртикулов.Область("R1C1");
Шапка.Текст = НСтр("ru = 'Артикулы'");
Шапка.Шрифт = Новый Шрифт(, , Истина);
Шапка.ЦветФона = Новый Цвет(240, 240, 240);
Шапка.Обвести(Линия, Линия, Линия, Линия);
Шапка.ШиринаКолонки = 40;
ТаблицаАртикулов.ФиксацияСверху = 1;
КонецПроцедуры
// Считывает непустые значения первой колонки, начиная со строки под шапкой.
//
// Возвращаемое значение:
// Массив из Строка
//
&НаСервере
Функция ВведенныеАртикулы()
Артикулы = Новый Массив;
Для НомерСтроки = 2 По ТаблицаАртикулов.ВысотаТаблицы Цикл
ДобавитьАртикул(Артикулы, ТаблицаАртикулов.Область("R" + НомерСтроки + "C1").Текст);
КонецЦикла;
Возврат Артикулы;
КонецФункции
// Дописывает в таблицу артикулы из файла.
//
// Параметры:
// АдресФайла - Строка - адрес двоичных данных файла во временном хранилище.
// ИмяФайла - Строка - имя файла с расширением.
// ОписаниеОшибки - Строка - возвращаемый параметр: текст ошибки разбора файла,
// пустая строка при успешном чтении.
//
// Возвращаемое значение:
// Число - сколько значений добавлено.
//
&НаСервере
Функция ДобавитьАртикулыИзФайла(АдресФайла, ИмяФайла, ОписаниеОшибки)
ИзФайла = АртикулыИзФайла(АдресФайла, ИмяФайла, ОписаниеОшибки);
Если Не ПустаяСтрока(ОписаниеОшибки) Тогда
Возврат 0;
КонецЕсли;
НомерСтроки = Макс(ТаблицаАртикулов.ВысотаТаблицы, 1);
Для Каждого Артикул Из ИзФайла Цикл
НомерСтроки = НомерСтроки + 1;
ТаблицаАртикулов.Область("R" + НомерСтроки + "C1").Текст = Артикул;
КонецЦикла;
Возврат ИзФайла.Количество();
КонецФункции
// Разбирает файл и возвращает артикулы из первой колонки.
//
// Текстовые форматы читаются потоком из временного хранилища. Табличные форматы
// читаются через временный файл: вариант ТабличныйДокумент.Прочитать(<Поток>)
// поддерживает только MXL, MXL7 и ODS, а xls/xlsx доступны исключительно варианту
// «из файла», где формат определяется по расширению. Работа с каталогом временных
// файлов в безопасном режиме разрешена — проверить на конкретной конфигурации;
// если запрещена, из фильтра выбора файла нужно убрать xls и xlsx, а ODS читать
// потоком с явным указанием ТипФайлаТабличногоДокумента.ODS.
//
// Параметры:
// АдресФайла - Строка - адрес двоичных данных файла во временном хранилище.
// ИмяФайла - Строка - имя файла с расширением, по нему выбирается способ чтения.
// ОписаниеОшибки - Строка - возвращаемый параметр: текст ошибки разбора файла.
//
// Возвращаемое значение:
// Массив из Строка - непустые значения первой колонки, по одному на строку файла.
//
&НаСервереБезКонтекста
Функция АртикулыИзФайла(АдресФайла, ИмяФайла, ОписаниеОшибки)
Артикулы = Новый Массив;
ОписаниеОшибки = "";
Точка = СтрНайти(ИмяФайла, ".", НаправлениеПоиска.СКонца);
Расширение = ?(Точка = 0, "", НРег(Сред(ИмяФайла, Точка + 1)));
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресФайла);
Попытка
Если Расширение = "txt" ИЛИ Расширение = "csv" Тогда
ЗаполнитьИзТекстовогоФайла(Артикулы, ДвоичныеДанные, Расширение = "csv");
Иначе
ЗаполнитьИзТабличногоФайла(Артикулы, ДвоичныеДанные, Расширение);
КонецЕсли;
Исключение
Артикулы.Очистить();
ОписаниеОшибки = НСтр("ru = 'Не удалось прочитать файл: неподдерживаемый формат или файл поврежден.'");
ЗаписьЖурналаРегистрации(
НСтр("ru = 'Загрузка артикулов'", ОбщегоНазначения.КодОсновногоЯзыка()),
УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
Возврат Артикулы;
КонецФункции
// Читает первую колонку текстового файла. Для CSV значение берется до первого
// разделителя. Кодировка определяется платформой по BOM; при его отсутствии
// используется ANSI — так открываются файлы, выгруженные из Excel и 1С.
//
// Параметры:
// Артикулы - Массив из Строка - приемник.
// ДвоичныеДанные - ДвоичныеДанные - содержимое файла.
// ЭтоCSV - Булево - резать строку по разделителю «;».
//
&НаСервереБезКонтекста
Процедура ЗаполнитьИзТекстовогоФайла(Артикулы, ДвоичныеДанные, ЭтоCSV)
Кодировка = ?(ЕстьМеткаUTF8(ДвоичныеДанные), КодировкаТекста.UTF8, КодировкаТекста.ANSI);
Чтение = Новый ЧтениеТекста(ДвоичныеДанные.ОткрытьПотокДляЧтения(), Кодировка);
Попытка
СтрокаФайла = Чтение.ПрочитатьСтроку();
Пока СтрокаФайла <> Неопределено Цикл
Если ЭтоCSV Тогда
Колонки = СтрРазделить(СтрокаФайла, ";");
Значение = Колонки[0];
Иначе
Значение = СтрокаФайла;
КонецЕсли;
ДобавитьАртикул(Артикулы, Значение);
СтрокаФайла = Чтение.ПрочитатьСтроку();
КонецЦикла;
Исключение
Чтение.Закрыть();
ВызватьИсключение;
КонецПопытки;
Чтение.Закрыть();
КонецПроцедуры
// Читает первую колонку табличного файла (xlsx, xls, ods, mxl) через временный файл.
//
// Параметры:
// Артикулы - Массив из Строка - приемник.
// ДвоичныеДанные - ДвоичныеДанные - содержимое файла.
// Расширение - Строка - расширение без точки, задает формат чтения.
//
&НаСервереБезКонтекста
Процедура ЗаполнитьИзТабличногоФайла(Артикулы, ДвоичныеДанные, Расширение)
ИмяВременногоФайла = ПолучитьИмяВременногоФайла(?(ПустаяСтрока(Расширение), "mxl", Расширение));
ДвоичныеДанные.Записать(ИмяВременногоФайла);
ТабличныйДокумент = Новый ТабличныйДокумент;
Попытка
ТабличныйДокумент.Прочитать(ИмяВременногоФайла, СпособЧтенияЗначенийТабличногоДокумента.Значение);
Исключение
УдалитьФайлы(ИмяВременногоФайла);
ВызватьИсключение;
КонецПопытки;
УдалитьФайлы(ИмяВременногоФайла);
Для НомерСтроки = 1 По ТабличныйДокумент.ВысотаТаблицы Цикл
Ячейка = ТабличныйДокумент.Область(НомерСтроки, 1, НомерСтроки, 1);
ДобавитьАртикул(Артикулы, ?(Ячейка.СодержитЗначение, Ячейка.Значение, Ячейка.Текст));
КонецЦикла;
КонецПроцедуры
// Добавляет в массив очищенное значение артикула, если оно непустое.
//
// Числовые значения приводятся к строке без разделителей групп: иначе артикул
// 12345678 превратился бы в «12 345 678».
//
// Параметры:
// Артикулы - Массив из Строка - приемник.
// Значение - Произвольный - исходное значение ячейки или строки файла.
//
&НаСервереБезКонтекста
Процедура ДобавитьАртикул(Артикулы, Значение)
Если ТипЗнч(Значение) = Тип("Число") Тогда
Артикул = СокрЛП(Формат(Значение, "ЧГ=0; ЧН="));
Иначе
Артикул = СокрЛП(Строка(Значение));
КонецЕсли;
Если Не ПустаяСтрока(Артикул) Тогда
Артикулы.Добавить(Артикул);
КонецЕсли;
КонецПроцедуры
// Проверяет наличие сигнатуры UTF-8 (BOM) в начале данных.
//
// Параметры:
// ДвоичныеДанные - ДвоичныеДанные - содержимое файла.
//
// Возвращаемое значение:
// Булево
//
&НаСервереБезКонтекста
Функция ЕстьМеткаUTF8(ДвоичныеДанные)
Если ДвоичныеДанные.Размер() < 3 Тогда
Возврат Ложь;
КонецЕсли;
Буфер = ДвоичныеДанные.ОткрытьПотокДляЧтения().Прочитать(3).ЗакрытьИПолучитьБуферДвоичныхДанных();
Возврат Буфер[0] = 239 И Буфер[1] = 187 И Буфер[2] = 191;
КонецФункции
#КонецОбласти