Как запретить создание дублей номенклатуры в 1С:Розница при ручном вводе и загрузке из Excel

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

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

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

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

Прежде чем переходить к написанию кода, проанализируем, какими путями новые элементы попадают в справочник Номенклатура. Существует два основных сценария:

  1. Интерактивный ввод: пользователь вручную открывает форму элемента, заполняет наименование и нажимает кнопку «Записать и закрыть».
  2. Программный ввод: внешняя обработка (например, парсер Excel-файлов или модуль интеграции с сайтом) создает объект на уровне кода, заполняет реквизиты и вызывает метод Записать().

Если для интерактивного ввода достаточно написать проверку в модуле справочника или включить стандартные функции БСП (Библиотеки стандартных подсистем), то программный ввод таит в себе скрытую угрозу — свойство ОбменДанными.Загрузка.

Рассмотрим, как ведет себя система в обоих случаях и как реализовать надежный заслон для дублей в каждом из них.

Решение 1. Контроль уникальности при ручном вводе (Модуль справочника)

Для того чтобы гарантированно заблокировать запись элемента с уже существующим наименованием при ручном вводе, мы должны использовать обработчик события ПередЗаписью в модуле менеджера или модуле объекта справочника Номенклатура. Наиболее гибким является использование модуля объекта.

Давайте разберем по шагам логику правильной проверки. Недостаточно просто написать запрос к базе данных. Проверка должна соответствовать следующим требованиям:

Ниже представлен профессиональный, оптимизированный шаблон кода для размещения в модуле объекта справочника Номенклатура:


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

Рассмотрим подробнее ключевые особенности этого кода. Использование метода СообщитьПользователю из общего модуля ОбщегоНазначенияКлиентСервер позволяет не просто вывести серый текст в окно сообщений, а визуально подчеркнуть поле «Наименование» красным цветом на управляемой форме. Это значительно улучшает UX (пользовательский опыт) карточки товара.

Решение 2. Проблема флага «ОбменДанными.Загрузка = Истина» при импорте из Excel

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


НовыйТовар = Справочники.Номенклатура.СоздатьЭлемент();
НовыйТовар.Наименование = СтрокаExcel.Наименование;
НовыйТовар.ОбменДанными.Загрузка = Истина; // Вот здесь кроется главная опасность!
НовыйТовар.Записать();

Зачем разработчики обработок пишут строку НовыйТовар.ОбменДанными.Загрузка = Истина? Это системный механизм платформы 1С:Предприятие, предназначенный для распределенных баз данных (РИБ). Он сообщает системе: «Этот объект уже был проверен и записан в другой базе, сейчас мы просто синхронизируем данные, поэтому выполнять тяжелые проверки бизнес-логики и заполнения реквизитов не нужно».

Если этот флаг установлен, то платформа автоматически игнорирует любые проверки внутри обработчика ПередЗаписью, если в начале процедуры стоит стандартная заглушка:


Если ОбменДанными.Загрузка Тогда
    Возврат;
КонецЕсли;

Если мы просто удалим эту заглушку из модуля справочника, мы нарушим стандарты разработки 1С и полностью заблокируем нормальную работу распределенных баз данных (РИБ). Синхронизация данных начнет падать с ошибками. Как же поступить правильно?

Существует два пути решения этой коллизии:

  1. Корректировка кода внешней обработки: необходимо удалить или закомментировать строку ОбменДанными.Загрузка = Истина в логике импорта из Excel. В этом случае при вызове метода Записать() обработка будет инициировать стандартный вызов обработчика ПередЗаписью, и наш код контроля дублей успешно отработает, выдав ошибку записи.
  2. Программная обработка дублей внутри самого алгоритма загрузки: этот метод гораздо более предпочтителен с точки зрения производительности и юзабилити. Вместо того чтобы вызывать падение платформенного метода записи по ошибке, обработка должна превентивно проверять наличие товара в базе.

Решение 3. Правильный алгоритм загрузки из Excel без запросов в цикле

Рассмотрим критическую ошибку многих начинающих программистов. Очень часто проверку на дубли при импорте пишут прямо внутри цикла обхода строк таблицы Excel. Схема выглядит так: прочитали строку -> сделали запрос к базе -> если не нашли, создали элемент.

Если в вашем файле импорта содержится 10 000 позиций, система выполнит 10 000 отдельных запросов к СУБД. Это приведет к колоссальным задержкам, зависанию интерфейса и жестким блокировкам таблиц базы данных. Настоятельно рекомендуем использовать пакетную загрузку с кэшированием данных.

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

  1. Сначала мы считываем данные из Excel-файла во временную таблицу значений в оперативной памяти.
  2. Затем мы выгружаем список всех наименований из файла в массив и передаем его ОДНИМ запросом к базе данных, чтобы сразу получить все существующие элементы.
  3. Загружаем результаты запроса в структуру соответствия (кэш), где ключом будет наименование товара, а значением — ссылка на элемент справочника.
  4. В цикле создания позиций мы обращаемся не к базе данных, а к нашему быстрому кэшу в оперативной памяти.

Проанализируем пример кода, реализующий эту производительную методику:


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

Используя этот паттерн проектирования, мы решаем сразу две задачи: полностью исключаем появление дублей при импорте сторонних прайс-листов и сохраняем максимальное быстродействие системы, сводя количество обращений к СУБД к абсолютному минимуму (всего один тяжелый запрос на получение соответствий).

Типовые средства БСП: Поиск и удаление дублей

Что делать, если дублирующие позиции уже успели попасть в вашу базу данных до момента внедрения жесткого контроля программного кода? В конфигурации «1С:Розница» (редакций 2.3 и 3.0) встроен мощный штатный механизм Библиотеки стандартных подсистем (БСП) для исправления таких ошибок.

Рассмотрим алгоритм действий по очистке информационной базы от дублирующихся карточек номенклатуры:

  1. Перейдем в раздел Администрирование, затем выберем пункт Обслуживание.
  2. В открывшемся меню найдем подраздел Корректировка данных и запустим обработку Поиск и удаление дублей.
  3. В поле «Искать в:» укажем справочник Номенклатура.
  4. В поле «Сравнивать:» настроим правила поиска. Мы можем задать совпадение по наименованию (как точное, так и по похожим словам), по артикулу или по штрихкоду.
  5. Нажмем кнопку «Найти дубли». Система просканирует всю базу и сгруппирует похожие позиции.
  6. В списке результатов система автоматически предложит один элемент в качестве «Оригинала» (обычно это элемент с наибольшим количеством связей в документах), а остальные пометит как «Дубли». При необходимости мы можем вручную переназначить оригинал.
  7. Нажмем кнопку «Удалить дубли». Система совершит сложнейшую транзакцию: она найдет абсолютно все упоминания дублирующих ссылок во всех документах базы данных (поступления, реализации, чеки ККМ, остатки, регистры сведений) и автоматически заменит их на ссылку выбранного Оригинала. После этого дублирующие элементы будут помечены на удаление.

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

← На главную