В процессе складского и торгового учета нередко возникают ситуации, когда операторы или менеджеры совершают массовые ошибки при вводе данных. Например, при оприходовании новой партии товаров штрихкоды могут быть ошибочно привязаны не к тому складу или преждевременно получить статус оприходованных. В таких случаях перед разработчиком встает задача: быстро и безопасно обновить реквизиты сотен или тысяч элементов справочника на основе реестра, предоставленного в формате Excel. При этом важно соблюсти бизнес-логику информационной системы — например, не трогать те позиции, которые уже были проданы клиентам.
В этой статье мы подробно разберем, как решить данную задачу оптимальным способом. Мы проанализируем типичные ошибки клиент-серверного взаимодействия, которые часто допускают начинающие разработчики, рассмотрим правильные шаблоны программирования на платформе 1С:Предприятие 8.3 и изучим альтернативный вариант решения проблемы вообще без написания кода.
Проанализируем ситуацию, описанную автором темы на форуме. Имеется справочник ШтрихКодыКовров, в котором есть реквизиты Склад (ссылка на справочник складов), Оприходован (Булево) и Продан (Булево). Нам предоставлен Excel-файл со списком штрихкодов для конкретного склада, по которым нужно снять флаг Оприходован (установить в значение Ложь), но только при условии, что ковер еще не продан (реквизит Продан имеет значение Ложь).
Первые версии кода, предложенные в обсуждении, используют технологию COM-соединения для чтения файла Excel и выполняют запись объектов непосредственно в цикле обхода строк файла на клиенте. Выясним причину, почему такой подход считается устаревшим и неэффективным:
COMОбъект("Excel.Application") требует обязательного наличия установленного пакета Microsoft Excel на компьютере пользователя. Этот код принципиально не будет работать на операционных системах семейства Linux, в веб-клиенте через браузер или в облачных сервисах.ЗаписатьПризнакОприходования вызывается внутри клиентского цикла), система будет совершать сотни сетевых запросов туда-обратно. База данных начнет "зависать", и обработка даже 500 строк может растянуться на долгие минуты.Посмотрим на пример правильной архитектуры решения, которая устраняет все эти недостатки.
Современным стандартом для чтения файлов формата .xls и .xlsx в среде 1С является использование платформенного объекта ТабличныйДокумент. Он работает непосредственно на сервере (в том числе под Linux), не требует сторонних программ и считывает файлы в память практически мгновенно.
Чтобы передать выбранный на клиенте файл на сервер, мы воспользуемся временным хранилищем. Разберем этот процесс по шагам.
Сначала на клиенте мы организуем выбор файла пользователем и передачу его двоичных данных на сервер:
&НаКлиенте
Процедура ВыбратьИОбработатьФайл(Команда)
// Проверим заполнение обязательного реквизита Склад на форме обработки
Если Не ЗначениеЗаполнено(Объект.Склад) Тогда
ПоказатьПредупреждение(, "Пожалуйста, выберите склад перед началом обработки!");
Возврат;
КонецЕсли;
// Настраиваем диалог выбора файла
Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
Диалог.Фильтр = "Файлы Excel (*.xls;*.xlsx)|*.xls;*.xlsx";
Диалог.Заголовок = "Выберите файл со штрихкодами";
Если Диалог.Выбрать() Тогда
// Помещаем файл во временное хранилище в виде двоичных данных
АдресФайлаВоВременномХранилище = "";
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(Диалог.ПолноеИмяФайла);
АдресФайлаВоВременномХранилище = ПоместитьВоВременноеХранилище(ДвоичныеДанныеФайла, УникальныйИдентификатор);
// Передаем управление серверному методу для быстрой обработки
ОбработатьДанныеНаСервере(АдресФайлаВоВременномХранилище, Объект.Склад);
КонецЕсли;
КонецПроцедуры
Теперь рассмотрим подробнее серверную процедуру. Нам необходимо решить две задачи: быстро извлечь штрихкоды из табличного документа и пакетно обновить информацию в базе данных.
Для ускорения поиска мы не будем искать каждый штрихкод в цикле через метод базы данных. Вместо этого мы соберем все штрихкоды в один массив, передадим его в запрос, найдем подходящие ссылки справочника одним пакетом, а затем выполним их изменение в рамках единой транзакции базы данных. Это ускорит выполнение задачи в сотни раз.
Посмотрим на пример реализации серверной логики:
&НаСервереБезКонтекста
Процедура ОбработатьДанныеНаСервере(Знач АдресФайла, Знач ВыбранныйСклад)
// Получаем файл из временного хранилища и сохраняем его во временный файл на сервере
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресФайла);
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xlsx");
ДвоичныеДанные.Записать(ИмяВременногоФайла);
// Считываем файл в ТабличныйДокумент
ТабДок = Новый ТабличныйДокумент;
Попытка
ТабДок.Прочитать(ИмяВременногоФайла, СпособЧтенияЗначенийТабличногоДокумента.Значение);
Исключение
ВызватьИсключение "Не удалось прочитать загруженный файл Excel. Ошибка: " + ОписаниеОшибки();
КонецПопытки;
// Извлекаем коды из первой или второй колонки (в зависимости от структуры вашего Excel)
// Предположим, что коды находятся во 2-й колонке, начиная со 2-й строки
МассивШтрихкодов = Новый Массив;
ВсегоСтрок = ТабДок.ВысотаТаблицы;
Для НомерСтроки = 2 По ВсегоСтрок Цикл
КодШК = СокрЛП(ТабДок.Область(НомерСтроки, 2).Текст);
Если ЗначениеЗаполнено(КодШК) Тогда
// Применяем форматирование, избавляясь от лишних пробелов и неразрывных символов
КодШК = СтрЗаменить(КодШК, Символы.НПП, "");
МассивШтрихкодов.Добавить(КодШК);
КонецЕсли;
КонецЦикла;
// Если список пуст, завершаем работу
Если МассивШтрихкодов.Количество() = 0 Тогда
Сообщить("В файле не обнаружено штрихкодов для обработки.");
Возврат;
КонецЕсли;
// Отыскиваем элементы справочника с помощью эффективного SQL-запроса
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ШтрихКодыКовров.Ссылка КАК Ссылка
|ИЗ
| Справочник.ШтрихКодыКовров КАК ШтрихКодыКовров
|ГДЕ
| ШтрихКодыКовров.Код В (&МассивШтрихкодов)
| И ШтрихКодыКовров.Склад = &Склад
| И ШтрихКодыКовров.Оприходован = ИСТИНА
| И ШтрихКодыКовров.Продан = ЛОЖЬ";
Запрос.УстановитьПараметр("МассивШтрихкодов", МассивШтрихкодов);
Запрос.УстановитьПараметр("Склад", ВыбранныйСклад);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
КоличествоИзмененных = 0;
// Начинаем транзакцию для максимального ускорения записи в базу данных
НачатьТранзакцию();
Попытка
Пока Выборка.Следующий() Цикл
// Получаем объект для редактирования
ОбъектШК = Выборка.Ссылка.ПолучитьОбъект();
ОбъектШК.Оприходован = Ложь;
// Записываем изменения в базу данных
ОбъектШК.Записать();
КоличествоИзмененных = КоличествоИзмененных + 1;
КонецЦикла;
// Фиксируем транзакцию, если все объекты успешно записались
ЗафиксироватьТранзакцию();
Сообщить("Обработка успешно завершена! Изменено элементов: " + КоличествоИзмененных);
Исключение
// В случае любой непредвиденной ошибки откатываем изменения обратно
ОтменитьТранзакцию();
Сообщить("В процессе записи данных произошла критическая ошибка. Все изменения отменены.");
Сообщить(ОписаниеОшибки());
КонецПопытки;
// Удаляем временный файл с диска сервера
Попытка
УдалитьФайлы(ИмяВременногоФайла);
Исключение
// Ошибку удаления временного файла можно проигнорировать
КонецПопытки;
КонецПроцедуры
Обратите внимание на использование транзакции с помощью конструкций НачатьТранзакцию(), ЗафиксироватьТранзакцию() и ОтменитьТранзакцию(). При записи 500-2000 элементов справочника этот прием сокращает общее время дисковых операций СУБД в десятки раз, превращая длительный процесс в секундное дело.
Иногда написание внешней обработки нецелесообразно, особенно если это разовая операция, которую нужно выполнить "здесь и сейчас". Посмотрим, как решить задачу силами стандартного функционала программ 1С.
Для этой цели отлично подойдет универсальная обработка "Загрузка данных из табличного документа" (ее можно бесплатно скачать на портале ИТС или найти в составе многих типовых конфигураций в разделе администрирования).
Разберем по шагам порядок действий для выполнения этой настройки:
Штрихкоды ковров (или ваше аналогичное название справочника).Код (или Штрихкод) укажите номер колонки, куда вы вставили данные, и установите флажок "Поле поиска".Оприходован снимите галочку привязки к колонке. Вместо этого в поле "Устанавливать значение" выберите фиксированное значение Ложь.Если Объект.Продан Или Объект.Склад <> Параметры.ВыбранныйСклад Тогда Отказ = Истина; КонецЕсли;.Использование этого универсального инструмента экономит драгоценное время разработчика и избавляет от необходимости засорять базу данных одноразовыми самописными обработками.