Как проверить наличие штрихкода у номенклатуры перед созданием нового в 1С?

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

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

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

Анализ проблемы: почему простой метод чтения через Менеджер Записи вызывает ошибку?

Начинающие разработчики часто пытаются использовать объект РегистрСведенийМенеджерЗаписи для быстрой проверки существования записи в базе данных. Логика кажется простой: заполнить ключевые поля (измерения), вызвать метод Прочитать(), и если метод вернул заполненный объект (или Выбран() возвращает Истину), то запись существует.

Давайте посмотрим на типичный ошибочный код, с которым столкнулся автор темы:


Для каждого Строка из ТабличноеПолеШК Цикл
    новШк = РегистрыСведений.Штрихкоды.СоздатьМенеджерЗаписи();
    Объект = Строка.Номенклатура;
    
    новШк.Владелец         = Объект.Ссылка;
    новШк.ЕдиницаИзмерения = Объект.ЕдиницаХраненияОстатков;
    новШк.ТипШтрихкода     = ПланыВидовХарактеристик.ТипыШтрихкодов.EAN13;
    новШк.Качество         = Справочники.Качество.Новый;
    новШк.Штрихкод         = РаботаСТорговымОборудованием.СформироватьШтрихКод();
    
    новШк.Прочитать(); // ОШИБКА ЗДЕСЬ!
    
    Если новШк.Выбран() Тогда
        Сообщить("Для номенклатуры " + Объект.Наименование + " уже присвоен штрихкод!");
        Продолжить;
    КонецЕсли;    
    
    новШк.Записать();
КонецЦикла;

При выполнении этого кода система выдает критическую ошибку: "Запись не верна! Значение поля 'Владелец' не может быть пустым!".

Выясним причину такого поведения платформы 1С. Дело в том, что метод Прочитать() у объекта РегистрСведенийМенеджерЗаписи работает следующим образом: он пытается найти в базе данных запись по указанному набору измерений. Если запись не найдена, менеджер записи полностью очищает все свои свойства (включая те измерения, которые вы только что заполнили перед вызовом чтения). В результате все поля менеджера становятся пустыми. Когда управление доходит до метода Записать(), платформа пытается записать пустой объект и выдает ошибку заполнения обязательных полей (измерений).

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

Решение проблемы с помощью Набора Записей (подход из форума)

Чтобы обойти ограничение менеджера записи и проверить наличие штрихкода по конкретному владельцу (номенклатуре), можно использовать объект РегистрСведенийНаборЗаписей. Мы устанавливаем отбор по номенклатуре, считываем данные, и если количество записей в наборе больше нуля — значит, у товара уже есть штрихкод.

Рассмотрим рабочий вариант кода, предложенный участниками обсуждения:


Для каждого Строка из ТабличноеПолеШК Цикл // ТабличноеПолеШК - таблица значений на форме
    
    Ссылка = Строка.Номенклатура; // Ссылка на номенклатуру
    
    // Проверка заполнения номенклатуры во избежание ошибок
    Если Ссылка.Пустая() Тогда
        Продолжить;
    КонецЕсли; 
    
    // Проверим, назначен ли штрих-код через набор записей
    НаборЗаписей = РегистрыСведений.Штрихкоды.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.Владелец.Установить(Ссылка);
    НаборЗаписей.Прочитать();
    
    Если НаборЗаписей.Количество() = 0 Тогда
        
        // Если не назначен, генерируем и добавляем новый штрих-код
        НовШк = РегистрыСведений.Штрихкоды.СоздатьМенеджерЗаписи();
        НовШк.Владелец         = Ссылка;
        НовШк.ТипШтрихкода     = ПланыВидовХарактеристик.ТипыШтрихкодов.EAN13;
        НовШк.ЕдиницаИзмерения = Ссылка.ЕдиницаХраненияОстатков;
        НовШк.Качество         = Справочники.Качество.Новый;        
        НовШк.Штрихкод         = РаботаСТорговымОборудованием.СформироватьШтрихКод();
        
        НовШк.Записать();
        
        Сообщить("Для номенклатуры " + Ссылка.Наименование + " назначен штрихкод: " + НовШк.Штрихкод);    
        
    Иначе
        // Штрих-код уже назначен, выводим лог и пропускаем строку
        Сообщить("Для номенклатуры " + Ссылка.Наименование + " штрихкод уже назначен!");
        Продолжить;
    КонецЕсли;    
    
КонецЦикла;

Этот код успешно решает задачу в рамках старых или кастомных конфигураций (например, УТ 10.3 или УПП 1.3), где измерением регистра является Владелец.

Критическая проблема производительности: Запрос в цикле

Несмотря на то, что приведенный выше код работает корректно на небольших объемах данных, он содержит грубую архитектурную ошибку — обращение к базе данных в цикле (метод НаборЗаписей.Прочитать() выполняется внутри цикла Для каждого). Если в вашей таблице будет 1 000 или 10 000 товаров, программа выполнит 10 000 отдельных транзакций к СУБД для проверки существования записей. Это приведет к сильному зависанию интерфейса и колоссальной нагрузке на сервер.

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

Разберем этот профессиональный подход по шагам:


// Шаг 1: Получаем массив всех товаров из таблицы значений
МассивНоменклатуры = ТабличноеПолеШК.ВыгрузитьКолонку("Номенклатура");

// Исключаем пустые ссылки из массива для корректности запроса
Индекс = МассивНоменклатуры.Количество() - 1;
Пока Индекс >= 0 Цикл
    Если НЕ ЗначениеЗаполнено(МассивНоменклатуры[Индекс]) Тогда
        МассивНоменклатуры.Удалить(Индекс);
    КонецЕсли;
    Индекс = Индекс - 1;
КонецЦикла;

Если МассивНоменклатуры.Количество() = 0 Тогда
    Возврат; // Нечего обрабатывать
КонецЕсли;

// Шаг 2: Выполняем ОДИН запрос для поиска товаров БЕЗ штрихкодов
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	НоменклатураСписок.Ссылка КАК Номенклатура,
    |	НоменклатураСписок.ЕдиницаХраненияОстатков КАК ЕдиницаИзмерения
    |ИЗ
    |	Справочник.Номенклатура КАК НоменклатураСписок
    |		ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Штрихкоды КАК Штрихкоды
    |		ПО НоменклатураСписок.Ссылка = Штрихкоды.Владелец
    |ГДЕ
    |	НоменклатураСписок.Ссылка В(&МассивНоменклатуры)
    |	И Штрихкоды.Штрихкод ЕСТЬ NULL";

Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
РезультатЗапроса = Запрос.Выполнить().Выбрать();

// Шаг 3: Цикл генерации запускаем только для отфильтрованного списка
Пока РезультатЗапроса.Следующий() Цикл
    
    // Генерируем штрихкод
    НовыйШтрихкод = РаботаСТорговымОборудованием.СформироватьШтрихКод();
    
    Если ЗначениеЗаполнено(НовыйШтрихкод) Тогда
        
        НовШк = РегистрыСведений.Штрихкоды.СоздатьМенеджерЗаписи();
        НовШк.Владелец         = РезультатЗапроса.Номен;
        НовШк.ТипШтрихкода     = ПланыВидовХарактеристик.ТипыШтрихкодов.EAN13;
        НовШк.ЕдиницаИзмерения = РезультатЗапроса.ЕдиницаИзмерения;
        НовШк.Качество         = Справочники.Качество.Новый;        
        НовШк.Штрихкод         = НовыйШтрихкод;
        
        Попытка
            НовШк.Записать();
            Сообщить("Для номенклатуры " + СокрЛП(РезультатЗапроса.Номенклатура) + " успешно создан штрихкод: " + НовыйШтрихкод);
        Исключение
            Сообщить("Не удалось записать штрихкод для: " + СокрЛП(РезультатЗапроса.Номенклатура) + ". Описание: " + ОписаниеОшибки());
        КонецПопытки;
        
    КонецЕсли;
    
КонецЦикла;

Преимущества оптимизированного метода:

  1. Количество обращений к базе данных для проверки снижено с N (где N — число строк) до 1.
  2. В цикле происходит только непосредственная запись новых кодов, что экономит процессорное время сервера и СУБД.
  3. Использование блока Попытка...Исключение защищает выполнение обработки от аварийного завершения, если при записи одного из штрихкодов возникнет непредвиденная системная блокировка.

Важные нюансы: Учет характеристик и архитектура современных конфигураций (УТ 11, ERP, УНФ)

При переносе данного кода в современные конфигурации 1С на платформе 8.3 (управляемые формы) следует учитывать важные архитектурные изменения:

  1. Регистр ШтрихкодыНоменклатуры: В новых версиях 1С (УТ 11, ERP 2, КА 2, УНФ) регистр сведений называется ШтрихкодыНоменклатуры. В отличие от старых версий, его ведущим измерением является сам Штрихкод (строковый тип), а номенклатура, характеристики и упаковки могут выступать реквизитами или другими измерениями. В такой структуре проверка через установку отбора набора записей по владельцу не всегда применима напрямую, и проверка через запрос становится единственным верным решением.
  2. Учет Характеристик и Упаковок: На современных складах у одного товара может быть несколько штрихкодов для разных упаковок (коробка, паллета, штука) и разных характеристик (цвет, размер). Если вы проверяете наличие штрихкода только по номенклатуре (владельцу), вы можете ошибочно пропустить генерацию кодов для новых упаковок товара. В запросе левое соединение необходимо делать по связке полей: Номенклатура + Характеристика + Упаковка.
← На главную