Появление дублирующихся позиций в справочнике Номенклатура — одна из самых частых и неприятных проблем в розничной торговле. Дубли ломают партионный учет, приводят к пересортице на складах, искажают аналитику продаж и вызывают путаницу у кассиров. Ситуация усложняется, когда в конфигурацию внедряются сторонние инструменты автоматизации, например, внешние обработки загрузки прайс-листов или накладных из Excel. Даже если в самой системе настроены ограничения, некорректно написанный код импорта может легко обойти все запреты.
В этой статье мы подробно разберем, почему возникают дубли, как правильно организовать проверку уникальности наименования на уровне СУБД, почему внешние обработки игнорируют стандартные механизмы защиты и как написать оптимальный код контроля без ущерба для производительности системы.
Прежде чем переходить к написанию кода, проанализируем, какими путями новые элементы попадают в справочник Номенклатура. Существует два основных сценария:
Записать().Если для интерактивного ввода достаточно написать проверку в модуле справочника или включить стандартные функции БСП (Библиотеки стандартных подсистем), то программный ввод таит в себе скрытую угрозу — свойство ОбменДанными.Загрузка.
Рассмотрим, как ведет себя система в обоих случаях и как реализовать надежный заслон для дублей в каждом из них.
Для того чтобы гарантированно заблокировать запись элемента с уже существующим наименованием при ручном вводе, мы должны использовать обработчик события ПередЗаписью в модуле менеджера или модуле объекта справочника Номенклатура. Наиболее гибким является использование модуля объекта.
Давайте разберем по шагам логику правильной проверки. Недостаточно просто написать запрос к базе данных. Проверка должна соответствовать следующим требованиям:
РАЗРЕШЕННЫЕ ПЕРВЫЕ 1).Ниже представлен профессиональный, оптимизированный шаблон кода для размещения в модуле объекта справочника Номенклатура:
Процедура ПередЗаписью(Отказ)
// Если запись происходит в режиме обмена данными, стандартные проверки бизнес-логики отключаются.
// Мы вернемся к этому механизму в следующем разделе статьи.
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Нам нужно выполнять тяжелый запрос к СУБД только в том случае, если наименование действительно изменилось.
НаименованиеИзменилось = ЭтоНовый() Или (Наименование <> Ссылка.Наименование);
Если НаименованиеИзменилось Тогда
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Наименование = &Наименование
| И Номенклатура.Ссылка <> &Ссылка";
// Передаем параметры в запрос. Исключаем текущую ссылку, чтобы объект не находил сам себя.
Запрос.УстановитьПараметр("Наименование", СокрЛП(Наименование));
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
// Выводим информативное сообщение пользователю
ТекстСообщения = ШаблонНСтр(
"ru = 'Ошибка сохранения! Номенклатура с наименованием ""%1"" уже зарегистрирована в системе.'"
);
ТекстСообщения = СтрШаблон(ТекстСообщения, Наименование);
// Используем стандартный механизм БСП для вывода сообщения, привязанного к конкретному реквизиту формы
ОбщегоНазначенияКлиентСервер.СообщитьПользователю(
ТекстСообщения,
Ссылка,
"Наименование",
,
Отказ
);
// Установка флага Отказ в Истина прерывает транзакцию записи
Отказ = Истина;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Рассмотрим подробнее ключевые особенности этого кода. Использование метода СообщитьПользователю из общего модуля ОбщегоНазначенияКлиентСервер позволяет не просто вывести серый текст в окно сообщений, а визуально подчеркнуть поле «Наименование» красным цветом на управляемой форме. Это значительно улучшает UX (пользовательский опыт) карточки товара.
Теперь проанализируем ситуацию, описанную участниками обсуждения. Очень часто внешние обработки загрузки данных используют следующий прием при создании программных объектов:
НовыйТовар = Справочники.Номенклатура.СоздатьЭлемент();
НовыйТовар.Наименование = СтрокаExcel.Наименование;
НовыйТовар.ОбменДанными.Загрузка = Истина; // Вот здесь кроется главная опасность!
НовыйТовар.Записать();
Зачем разработчики обработок пишут строку НовыйТовар.ОбменДанными.Загрузка = Истина? Это системный механизм платформы 1С:Предприятие, предназначенный для распределенных баз данных (РИБ). Он сообщает системе: «Этот объект уже был проверен и записан в другой базе, сейчас мы просто синхронизируем данные, поэтому выполнять тяжелые проверки бизнес-логики и заполнения реквизитов не нужно».
Если этот флаг установлен, то платформа автоматически игнорирует любые проверки внутри обработчика ПередЗаписью, если в начале процедуры стоит стандартная заглушка:
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
Если мы просто удалим эту заглушку из модуля справочника, мы нарушим стандарты разработки 1С и полностью заблокируем нормальную работу распределенных баз данных (РИБ). Синхронизация данных начнет падать с ошибками. Как же поступить правильно?
Существует два пути решения этой коллизии:
ОбменДанными.Загрузка = Истина в логике импорта из Excel. В этом случае при вызове метода Записать() обработка будет инициировать стандартный вызов обработчика ПередЗаписью, и наш код контроля дублей успешно отработает, выдав ошибку записи.Рассмотрим критическую ошибку многих начинающих программистов. Очень часто проверку на дубли при импорте пишут прямо внутри цикла обхода строк таблицы Excel. Схема выглядит так: прочитали строку -> сделали запрос к базе -> если не нашли, создали элемент.
Если в вашем файле импорта содержится 10 000 позиций, система выполнит 10 000 отдельных запросов к СУБД. Это приведет к колоссальным задержкам, зависанию интерфейса и жестким блокировкам таблиц базы данных. Настоятельно рекомендуем использовать пакетную загрузку с кэшированием данных.
Разберем этот продвинутый алгоритм по шагам:
Проанализируем пример кода, реализующий эту производительную методику:
Процедура ЗагрузитьДанныеИзExcel(ТаблицаДанныхФайла)
// Шаг 1. Собираем список уникальных наименований из загруженного файла
МассивНаименований = ТаблицаДанныхФайла.ВыгрузитьКолонку("Наименование");
// Удаляем дубликаты внутри самого массива загрузки, чтобы не искать одно и то же дважды
ОбщегоНазначенияКлиентСервер.СвернутьМассив(МассивНаименований);
// Шаг 2. Пакетным запросом выбираем из базы все уже существующие товары по списку наименований
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Наименование В (&МассивНаименований)";
Запрос.УстановитьПараметр("МассивНаименований", МассивНаименований);
ТаблицаСуществующих = Запрос.Выполнить().Выгрузить();
// Шаг 3. Строим кэш соответствия для мгновенного поиска в памяти
КэшНоменклатуры = Новый Соответствие;
Для Каждого СтрокаТаблицы Из ТаблицаСуществующих Цикл
// Приводим к единому регистру и убираем лишние пробелы для точности сравнения
КлючПоиска = ВРег(СокрЛП(СтрокаТаблицы.Наименование));
КэшНоменклатуры.Вставить(КлючПоиска, СтрокаТаблицы.Ссылка);
КонецЦикла;
// Шаг 4. Обходим строки Excel-файла и создаем только уникальные позиции
Для Каждого СтрокаДанных Из ТаблицаДанныхФайла Цикл
ТекущееИмя = СокрЛП(СтрокаДанных.Наименование);
КлючПоиска = ВРег(ТекущееИмя);
НайденнаяСсылка = КэшНоменклатуры.Получить(КлючПоиска);
Если НайденнаяСсылка = Неопределено Тогда
// Товар не найден в кэше — создаем новый элемент справочника
НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
НовыйЭлемент.Наименование = ТекущееИмя;
// Заполняем остальные обязательные реквизиты
НовыйЭлемент.Услуга = Ложь;
Попытка
НовыйЭлемент.Записать();
// Добавляем вновь созданный элемент в наш кэш,
// на случай если в Excel файле это наименование встретится еще раз
КэшНоменклатуры.Вставить(КлючПоиска, НовыйЭлемент.Ссылка);
Сообщить("Успешно создан новый товар: " + ТекущееИмя);
Исключение
Сообщить("Не удалось записать товар: " + ТекущееИмя + ". Ошибка: " + ОписаниеОшибки());
КонецПопытки;
Иначе
// Товар уже существует в базе. Мы можем обновить его реквизиты или просто пропустить создание.
Сообщить("Товар уже присутствует в базе (пропуск): " + ТекущееИмя);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Используя этот паттерн проектирования, мы решаем сразу две задачи: полностью исключаем появление дублей при импорте сторонних прайс-листов и сохраняем максимальное быстродействие системы, сводя количество обращений к СУБД к абсолютному минимуму (всего один тяжелый запрос на получение соответствий).
Что делать, если дублирующие позиции уже успели попасть в вашу базу данных до момента внедрения жесткого контроля программного кода? В конфигурации «1С:Розница» (редакций 2.3 и 3.0) встроен мощный штатный механизм Библиотеки стандартных подсистем (БСП) для исправления таких ошибок.
Рассмотрим алгоритм действий по очистке информационной базы от дублирующихся карточек номенклатуры:
Номенклатура.Этот инструмент позволяет навести идеальный порядок в справочниках без риска нарушить целостность ссылочной структуры вашей базы данных.