При автоматизации складского учета и интеграции с торговым оборудованием в 1С часто возникает задача массовой генерации штрихкодов для номенклатуры. Типичный сценарий: у нас есть список товаров (например, в табличной части обработки или в таблице значений), для которых нужно сформировать недостающие штрихкоды. При этом крайне важно пропускать те товары, у которых штрихкод уже назначен, чтобы не затереть существующие данные и не плодить дубли.
В этой статье мы подробно разберем, как правильно реализовать такую проверку, разберем типичные ошибки начинающих разработчиков при работе с менеджером записи регистра сведений и покажем профессиональный оптимизированный алгоритм для работы с большими объемами данных.
Начинающие разработчики часто пытаются использовать объект РегистрСведенийМенеджерЗаписи для быстрой проверки существования записи в базе данных. Логика кажется простой: заполнить ключевые поля (измерения), вызвать метод Прочитать(), и если метод вернул заполненный объект (или Выбран() возвращает Истину), то запись существует.
Давайте посмотрим на типичный ошибочный код, с которым столкнулся автор темы:
Для каждого Строка из ТабличноеПолеШК Цикл
новШк = РегистрыСведений.Штрихкоды.СоздатьМенеджерЗаписи();
Объект = Строка.Номенклатура;
новШк.Владелец = Объект.Ссылка;
новШк.ЕдиницаИзмерения = Объект.ЕдиницаХраненияОстатков;
новШк.ТипШтрихкода = ПланыВидовХарактеристик.ТипыШтрихкодов.EAN13;
новШк.Качество = Справочники.Качество.Новый;
новШк.Штрихкод = РаботаСТорговымОборудованием.СформироватьШтрихКод();
новШк.Прочитать(); // ОШИБКА ЗДЕСЬ!
Если новШк.Выбран() Тогда
Сообщить("Для номенклатуры " + Объект.Наименование + " уже присвоен штрихкод!");
Продолжить;
КонецЕсли;
новШк.Записать();
КонецЦикла;
При выполнении этого кода система выдает критическую ошибку: "Запись не верна! Значение поля 'Владелец' не может быть пустым!".
Выясним причину такого поведения платформы 1С. Дело в том, что метод Прочитать() у объекта РегистрСведенийМенеджерЗаписи работает следующим образом: он пытается найти в базе данных запись по указанному набору измерений. Если запись не найдена, менеджер записи полностью очищает все свои свойства (включая те измерения, которые вы только что заполнили перед вызовом чтения). В результате все поля менеджера становятся пустыми. Когда управление доходит до метода Записать(), платформа пытается записать пустой объект и выдает ошибку заполнения обязательных полей (измерений).
Кроме того, в данном случае поиск по измерению Штрихкод бессмысленен, так как на каждой итерации функция СформироватьШтрихКод() генерирует совершенно новое уникальное значение, которого заведомо нет в базе данных.
Чтобы обойти ограничение менеджера записи и проверить наличие штрихкода по конкретному владельцу (номенклатуре), можно использовать объект РегистрСведенийНаборЗаписей. Мы устанавливаем отбор по номенклатуре, считываем данные, и если количество записей в наборе больше нуля — значит, у товара уже есть штрихкод.
Рассмотрим рабочий вариант кода, предложенный участниками обсуждения:
Для каждого Строка из ТабличноеПолеШК Цикл // ТабличноеПолеШК - таблица значений на форме
Ссылка = Строка.Номенклатура; // Ссылка на номенклатуру
// Проверка заполнения номенклатуры во избежание ошибок
Если Ссылка.Пустая() Тогда
Продолжить;
КонецЕсли;
// Проверим, назначен ли штрих-код через набор записей
НаборЗаписей = РегистрыСведений.Штрихкоды.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Владелец.Установить(Ссылка);
НаборЗаписей.Прочитать();
Если НаборЗаписей.Количество() = 0 Тогда
// Если не назначен, генерируем и добавляем новый штрих-код
НовШк = РегистрыСведений.Штрихкоды.СоздатьМенеджерЗаписи();
НовШк.Владелец = Ссылка;
НовШк.ТипШтрихкода = ПланыВидовХарактеристик.ТипыШтрихкодов.EAN13;
НовШк.ЕдиницаИзмерения = Ссылка.ЕдиницаХраненияОстатков;
НовШк.Качество = Справочники.Качество.Новый;
НовШк.Штрихкод = РаботаСТорговымОборудованием.СформироватьШтрихКод();
НовШк.Записать();
Сообщить("Для номенклатуры " + Ссылка.Наименование + " назначен штрихкод: " + НовШк.Штрихкод);
Иначе
// Штрих-код уже назначен, выводим лог и пропускаем строку
Сообщить("Для номенклатуры " + Ссылка.Наименование + " штрихкод уже назначен!");
Продолжить;
КонецЕсли;
КонецЦикла;
Этот код успешно решает задачу в рамках старых или кастомных конфигураций (например, УТ 10.3 или УПП 1.3), где измерением регистра является Владелец.
Несмотря на то, что приведенный выше код работает корректно на небольших объемах данных, он содержит грубую архитектурную ошибку — обращение к базе данных в цикле (метод НаборЗаписей.Прочитать() выполняется внутри цикла Для каждого). Если в вашей таблице будет 1 000 или 10 000 товаров, программа выполнит 10 000 отдельных транзакций к СУБД для проверки существования записей. Это приведет к сильному зависанию интерфейса и колоссальной нагрузке на сервер.
Проанализируем оптимальный способ решения этой задачи с помощью одного пакетного запроса. Мы сначала выгрузим весь список номенклатуры, одним запросом определим, у каких товаров еще нет штрихкодов, и выполним генерацию только для них.
Разберем этот профессиональный подход по шагам:
// Шаг 1: Получаем массив всех товаров из таблицы значений
МассивНоменклатуры = ТабличноеПолеШК.ВыгрузитьКолонку("Номенклатура");
// Исключаем пустые ссылки из массива для корректности запроса
Индекс = МассивНоменклатуры.Количество() - 1;
Пока Индекс >= 0 Цикл
Если НЕ ЗначениеЗаполнено(МассивНоменклатуры[Индекс]) Тогда
МассивНоменклатуры.Удалить(Индекс);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
Если МассивНоменклатуры.Количество() = 0 Тогда
Возврат; // Нечего обрабатывать
КонецЕсли;
// Шаг 2: Выполняем ОДИН запрос для поиска товаров БЕЗ штрихкодов
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| НоменклатураСписок.Ссылка КАК Номенклатура,
| НоменклатураСписок.ЕдиницаХраненияОстатков КАК ЕдиницаИзмерения
|ИЗ
| Справочник.Номенклатура КАК НоменклатураСписок
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Штрихкоды КАК Штрихкоды
| ПО НоменклатураСписок.Ссылка = Штрихкоды.Владелец
|ГДЕ
| НоменклатураСписок.Ссылка В(&МассивНоменклатуры)
| И Штрихкоды.Штрихкод ЕСТЬ NULL";
Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
РезультатЗапроса = Запрос.Выполнить().Выбрать();
// Шаг 3: Цикл генерации запускаем только для отфильтрованного списка
Пока РезультатЗапроса.Следующий() Цикл
// Генерируем штрихкод
НовыйШтрихкод = РаботаСТорговымОборудованием.СформироватьШтрихКод();
Если ЗначениеЗаполнено(НовыйШтрихкод) Тогда
НовШк = РегистрыСведений.Штрихкоды.СоздатьМенеджерЗаписи();
НовШк.Владелец = РезультатЗапроса.Номен;
НовШк.ТипШтрихкода = ПланыВидовХарактеристик.ТипыШтрихкодов.EAN13;
НовШк.ЕдиницаИзмерения = РезультатЗапроса.ЕдиницаИзмерения;
НовШк.Качество = Справочники.Качество.Новый;
НовШк.Штрихкод = НовыйШтрихкод;
Попытка
НовШк.Записать();
Сообщить("Для номенклатуры " + СокрЛП(РезультатЗапроса.Номенклатура) + " успешно создан штрихкод: " + НовыйШтрихкод);
Исключение
Сообщить("Не удалось записать штрихкод для: " + СокрЛП(РезультатЗапроса.Номенклатура) + ". Описание: " + ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
КонецЦикла;
Преимущества оптимизированного метода:
N (где N — число строк) до 1.Попытка...Исключение защищает выполнение обработки от аварийного завершения, если при записи одного из штрихкодов возникнет непредвиденная системная блокировка.При переносе данного кода в современные конфигурации 1С на платформе 8.3 (управляемые формы) следует учитывать важные архитектурные изменения:
ШтрихкодыНоменклатуры. В отличие от старых версий, его ведущим измерением является сам Штрихкод (строковый тип), а номенклатура, характеристики и упаковки могут выступать реквизитами или другими измерениями. В такой структуре проверка через установку отбора набора записей по владельцу не всегда применима напрямую, и проверка через запрос становится единственным верным решением.Номенклатура + Характеристика + Упаковка.