Как снять флаг "Оприходован" у штрихкодов в 1С на основе файла Excel?

Программист 1С v8.3 (Управляемые формы) Управленческий учет Торговля и дистрибуция
← На главную

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

В этой статье мы подробно разберем, как решить данную задачу оптимальным способом. Мы проанализируем типичные ошибки клиент-серверного взаимодействия, которые часто допускают начинающие разработчики, рассмотрим правильные шаблоны программирования на платформе 1С:Предприятие 8.3 и изучим альтернативный вариант решения проблемы вообще без написания кода.

Анализ исходной задачи и поиск причин неоптимальной работы

Проанализируем ситуацию, описанную автором темы на форуме. Имеется справочник ШтрихКодыКовров, в котором есть реквизиты Склад (ссылка на справочник складов), Оприходован (Булево) и Продан (Булево). Нам предоставлен Excel-файл со списком штрихкодов для конкретного склада, по которым нужно снять флаг Оприходован (установить в значение Ложь), но только при условии, что ковер еще не продан (реквизит Продан имеет значение Ложь).

Первые версии кода, предложенные в обсуждении, используют технологию COM-соединения для чтения файла Excel и выполняют запись объектов непосредственно в цикле обхода строк файла на клиенте. Выясним причину, почему такой подход считается устаревшим и неэффективным:

  1. Зависимость от операционной системы и стороннего ПО. Использование объекта COMОбъект("Excel.Application") требует обязательного наличия установленного пакета Microsoft Excel на компьютере пользователя. Этот код принципиально не будет работать на операционных системах семейства Linux, в веб-клиенте через браузер или в облачных сервисах.
  2. Огромные задержки из-за вызовов сервера в цикле. Если файл Excel считывается на клиенте, а запись каждого элемента справочника происходит через серверный вызов (как в сообщении 61: метод ЗаписатьПризнакОприходования вызывается внутри клиентского цикла), система будет совершать сотни сетевых запросов туда-обратно. База данных начнет "зависать", и обработка даже 500 строк может растянуться на долгие минуты.
  3. Отсутствие транзакционности. Запись каждого объекта по отдельности нагружает СУБД, так как под каждую операцию СУБД вынуждена открывать и закрывать отдельную транзакцию.

Посмотрим на пример правильной архитектуры решения, которая устраняет все эти недостатки.

Шаг 1. Переход на кроссплатформенное чтение Excel через ТабличныйДокумент

Современным стандартом для чтения файлов формата .xls и .xlsx в среде 1С является использование платформенного объекта ТабличныйДокумент. Он работает непосредственно на сервере (в том числе под Linux), не требует сторонних программ и считывает файлы в память практически мгновенно.

Чтобы передать выбранный на клиенте файл на сервер, мы воспользуемся временным хранилищем. Разберем этот процесс по шагам.

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


&НаКлиенте
Процедура ВыбратьИОбработатьФайл(Команда)
    
    // Проверим заполнение обязательного реквизита Склад на форме обработки
    Если Не ЗначениеЗаполнено(Объект.Склад) Тогда
        ПоказатьПредупреждение(, "Пожалуйста, выберите склад перед началом обработки!");
        Возврат;
    КонецЕсли;
    
    // Настраиваем диалог выбора файла
    Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
    Диалог.Фильтр = "Файлы Excel (*.xls;*.xlsx)|*.xls;*.xlsx";
    Диалог.Заголовок = "Выберите файл со штрихкодами";
    
    Если Диалог.Выбрать() Тогда
        
        // Помещаем файл во временное хранилище в виде двоичных данных
        АдресФайлаВоВременномХранилище = "";
        ДвоичныеДанныеФайла = Новый ДвоичныеДанные(Диалог.ПолноеИмяФайла);
        АдресФайлаВоВременномХранилище = ПоместитьВоВременноеХранилище(ДвоичныеДанныеФайла, УникальныйИдентификатор);
        
        // Передаем управление серверному методу для быстрой обработки
        ОбработатьДанныеНаСервере(АдресФайлаВоВременномХранилище, Объект.Склад);
        
    КонецЕсли;
    
КонецПроцедуры

Шаг 2. Разработка эффективного серверного алгоритма

Теперь рассмотрим подробнее серверную процедуру. Нам необходимо решить две задачи: быстро извлечь штрихкоды из табличного документа и пакетно обновить информацию в базе данных.

Для ускорения поиска мы не будем искать каждый штрихкод в цикле через метод базы данных. Вместо этого мы соберем все штрихкоды в один массив, передадим его в запрос, найдем подходящие ссылки справочника одним пакетом, а затем выполним их изменение в рамках единой транзакции базы данных. Это ускорит выполнение задачи в сотни раз.

Посмотрим на пример реализации серверной логики:


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

Обратите внимание на использование транзакции с помощью конструкций НачатьТранзакцию(), ЗафиксироватьТранзакцию() и ОтменитьТранзакцию(). При записи 500-2000 элементов справочника этот прием сокращает общее время дисковых операций СУБД в десятки раз, превращая длительный процесс в секундное дело.

Решение задачи без программирования: использование типовых инструментов

Иногда написание внешней обработки нецелесообразно, особенно если это разовая операция, которую нужно выполнить "здесь и сейчас". Посмотрим, как решить задачу силами стандартного функционала программ 1С.

Для этой цели отлично подойдет универсальная обработка "Загрузка данных из табличного документа" (ее можно бесплатно скачать на портале ИТС или найти в составе многих типовых конфигураций в разделе администрирования).

Разберем по шагам порядок действий для выполнения этой настройки:

  1. Откройте обработку и в поле "Режим загрузки" выберите вариант "Загрузка в справочник".
  2. В выпадающем списке справочников укажите Штрихкоды ковров (или ваше аналогичное название справочника).
  3. Скопируйте колонку со штрихкодами из вашего файла Excel и вставьте ее непосредственно в табличное поле на форме обработки.
  4. Перейдите на вкладку "Настройка параметров загрузки". Здесь система предложит сопоставить колонки таблицы с реквизитами справочника в базе:
    • Для реквизита Код (или Штрихкод) укажите номер колонки, куда вы вставили данные, и установите флажок "Поле поиска".
    • Для реквизита Оприходован снимите галочку привязки к колонке. Вместо этого в поле "Устанавливать значение" выберите фиксированное значение Ложь.
    • Чтобы случайно не перезаписать проданные товары или элементы других складов, перейдите в режим тонкой настройки обработки. В событиях загрузки (например, в событии "При записи") вы можете прописать короткое условие: Если Объект.Продан Или Объект.Склад <> Параметры.ВыбранныйСклад Тогда Отказ = Истина; КонецЕсли;.
  5. В нижней части формы обязательно установите важный флажок — "Не создавать новые элементы". Это гарантирует, что обработка будет работать исключительно в режиме обновления существующих записей, а не создавать новые некорректные штрихкоды при опечатках в файле.
  6. Нажмите кнопку "Загрузить". Программа сопоставит строки из табличного поля с базой данных по коду и точечно изменит признак оприходования у найденных элементов.

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

← На главную