В практической деятельности при импорте номенклатуры из внешних источников (прайс-листы поставщиков, файлы Excel, XML или API-выгрузки) часто возникает ситуация, когда у разных производителей совпадают артикулы товаров. Если в вашей обработке загрузки прописан жесткий поиск только по артикулу, система обнаружит уже существующий товар другого производителя и закрепит за ним новые данные либо вовсе откажется создавать новую карточку. В результате нарушается складской учет, а аналитика по брендам и производителям становится некорректной.
В данной статье мы подробно разберем, почему возникает эта проблема, проанализируем исходный код обработки, рассмотрим несколько вариантов решения (от простого отключения проверки до создания «умного» запроса по артикулу и производителю), а также разберем важные нюансы использования режима загрузки данных и архитектурные альтернативы 1С.
Рассмотрим структуру функции ПолучитьНоменклатуру, представленной в исходном сообщении темы. В самом начале функция пытается определить, существует ли уже товар в базе данных, выполняя следующий запрос:
Запрос = Новый Запрос;
Артикул = СтрокаТекст.Артикул;
Наименование = СтрокаТекст.Наименование;
Запрос.УстановитьПараметр("Артикул", Артикул);
Запрос.Текст = "ВЫБРАТЬ
| Номенклатура.Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Артикул = &Артикул
| И НЕ Номенклатура.ПометкаУдаления";
Выборка = Запрос.Выполнить.Выбрать;
Проанализируем логику этого фрагмента. Если в базе данных находится хотя бы один товар с аналогичным артикулом (даже если у него совершенно другой производитель или наименование), условие Если Выборка.Следующий() тогда возвращает Истина. Программа записывает найденную ссылку в переменную НоменклатураСсылка и завершает выполнение функции, пропуская блок Иначе, в котором должны создаваться новый производитель и новая карточка товара.
Это приводит к тому, что система ошибочно сопоставляет новую позицию с уже существующей чужой карточкой. Если же товара с таким артикулом в базе вообще нет, код отрабатывает корректно: создается и производитель, и сама номенклатурная позиция.
Самый простой способ, который часто выбирают разработчики для быстрого решения задачи, — это полное комментирование или удаление блока проверки уникальности артикула (как предложено во втором сообщении темы). В этом случае система вообще не будет искать существующие товары, а начнет сразу создавать новые элементы в справочнике Номенклатура.
Рассмотрим, как изменится код функции при таком подходе:
&НаСервере
Функция ПолучитьНоменклатуру(СтрокаТекст)
Артикул = СтрокаТекст.Артикул;
Наименование = СтрокаТекст.Наименование;
РодительСсылка = ГруппаНоменклатуры;
// Блок поиска по артикулу полностью удален.
// Переходим сразу к созданию производителя и карточки номенклатуры.
ТекстПроизводитель = СокрЛП(СтрокаТекст.Производитель);
Если ЗначениеЗаполнено(ТекстПроизводитель) Тогда
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("Наименование", ТекстПроизводитель);
Запрос.УстановитьПараметр("Владелец", ХарактеристикаПроизводитель);
Запрос.Текст = "ВЫБРАТЬ
| ЗначенияСвойствОбъектов.Ссылка КАК Ссылка
|ИЗ
| Справочник.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
|ГДЕ
| ЗначенияСвойствОбъектов.Наименование = &Наименование
| И ЗначенияСвойствОбъектов.Владелец = &Владелец";
ВыборкаПроизводитель = Запрос.Выполнить().Выбрать();
Если ВыборкаПроизводитель.Следующий() Тогда
ПроизводительСсылка = ВыборкаПроизводитель.Ссылка;
Иначе
ПроизводительОбъект = Справочники.ЗначенияСвойствОбъектов.СоздатьЭлемент();
ПроизводительОбъект.Владелец = ХарактеристикаПроизводитель;
ПроизводительОбъект.Наименование = ТекстПроизводитель;
Попытка
ПроизводительОбъект.Записать();
ПроизводительСсылка = ПроизводительОбъект.Ссылка;
Исключение
Сообщить(ОписаниеОшибки());
ПроизводительСсылка = Справочники.ЗначенияСвойствОбъектов.ПустаяСсылка();
КонецПопытки;
КонецЕсли;
Иначе
ПроизводительСсылка = Справочники.ЗначенияСвойствОбъектов.ПустаяСсылка();
КонецЕсли;
// Создание новой карточки товара
НоменклатураОбъект = Справочники.Номенклатура.СоздатьЭлемент();
НоменклатураОбъект.ТипНоменклатуры = Перечисления.ТипыНоменклатуры.Товар;
НоменклатураОбъект.ВидНоменклатуры = ВидНоменклатуры;
НоменклатураОбъект.Качество = Перечисления.ГрадацииКачества.Новый;
НоменклатураОбъект.СтавкаНДС = Перечисления.СтавкиНДС.БезНДС;
ЗаполнитьЗначенияСвойств(НоменклатураОбъект, ВидНоменклатуры, , "Ссылка, Владелец, ПометкаУдаления, ВерсияДанных, Предопределенный, ЭтоГруппа, Родитель, Описание, Наименование, Код, ИмяПредопределенныхДанных");
НоменклатураОбъект.Наименование = Наименование;
НоменклатураОбъект.НаименованиеПолное = Наименование;
НоменклатураОбъект.Артикул = Артикул;
НоменклатураОбъект.Родитель = РодительСсылка;
// Запись производителя в дополнительные реквизиты
ЗаписьПроизводитель = НоменклатураОбъект.ДополнительныеРеквизиты.Добавить();
ЗаписьПроизводитель.Значение = ПроизводительСсылка;
ЗаписьПроизводитель.Свойство = ХарактеристикаПроизводитель;
Попытка
НоменклатураОбъект.Записать();
НоменклатураСсылка = НоменклатураОбъект.Ссылка;
Исключение
Сообщить(ОписаниеОшибки());
НоменклатураСсылка = Справочники.Номенклатура.ПустаяСсылка();
КонецПопытки;
Возврат НоменклатураСсылка;
КонецФункции
Важное предупреждение: Использование этого метода крайне нежелательно при регулярных загрузках. Поскольку проверка существования товара полностью отключена, при каждом повторном запуске обработки система будет создавать дубликаты одних и тех же товаров. Это приведет к лавинообразному замусориванию справочника Номенклатура.
Чтобы избежать дублирования и корректно идентифицировать товары, необходимо выполнять поиск по связке «Артикул + Производитель». Однако здесь кроется важная техническая деталь, которую часто упускают начинающие разработчики.
В современных конфигурациях 1С (таких как «Управление торговлей 11», «ERP Управление предприятием» или «Комплексная автоматизация») реквизит Производитель чаще всего не является стандартным реквизитом справочника Номенклатура. Он хранится в табличной части ДополнительныеРеквизиты объекта.
Попытка выполнить простой запрос вида Номенклатура.Производитель = &Производитель вызовет ошибку отсутствия реквизита. Чтобы правильно реализовать поиск, мы должны соединить основную таблицу справочника с табличной частью его дополнительных реквизитов. Разберем по шагам, как это сделать программно.
Ниже представлен корректный код функции с предварительным поиском производителя и последующим поиском номенклатуры по сочетанию артикула и ссылки на производителя:
&НаСервере
Функция ПолучитьНоменклатуру(СтрокаТекст)
Артикул = СтрокаТекст.Артикул;
Наименование = СтрокаТекст.Наименование;
РодительСсылка = ГруппаНоменклатуры;
// Шаг 1. Сначала ищем или создаем производителя в базе данных,
// так как его ссылка понадобится нам для точного поиска номенклатуры.
ТекстПроизводитель = СокрЛП(СтрокаТекст.Производитель);
Если ЗначениеЗаполнено(ТекстПроизводитель) Тогда
ЗапросПроизводитель = Новый Запрос;
ЗапросПроизводитель.УстановитьПараметр("Наименование", ТекстПроизводитель);
ЗапросПроизводитель.УстановитьПараметр("Владелец", ХарактеристикаПроизводитель);
ЗапросПроизводитель.Текст =
"ВЫБРАТЬ
| ЗначенияСвойствОбъектов.Ссылка КАК Ссылка
|ИЗ
| Справочник.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
|ГДЕ
| ЗначенияСвойствОбъектов.Наименование = &Наименование
| И ЗначенияСвойствОбъектов.Владелец = &Владелец";
ВыборкаПроизводитель = ЗапросПроизводитель.Выполнить().Выбрать();
Если ВыборкаПроизводитель.Следующий() Тогда
ПроизводительСсылка = ВыборкаПроизводитель.Ссылка;
Else
ПроизводительОбъект = Справочники.ЗначенияСвойствОбъектов.СоздатьЭлемент();
ПроизводительОбъект.Владелец = ХарактеристикаПроизводитель;
ПроизводительОбъект.Наименование = ТекстПроизводитель;
Попытка
ПроизводительОбъект.Записать();
ПроизводительСсылка = ПроизводительОбъект.Ссылка;
Исключение
Сообщить("Ошибка при записи производителя: " + ОписаниеОшибки());
ПроизводительСсылка = Справочники.ЗначенияСвойствОбъектов.ПустаяСсылка();
КонецПопытки;
КонецЕсли;
Иначе
ПроизводительСсылка = Справочники.ЗначенияСвойствОбъектов.ПустаяСсылка();
КонецЕсли;
// Шаг 2. Ищем существующую номенклатуру по сочетанию Артикула и Производителя
НоменклатураСсылка = Справочники.Номенклатура.ПустаяСсылка();
ЗапросНоменклатура = Новый Запрос;
ЗапросНоменклатура.УстановитьПараметр("Артикул", Артикул);
ЗапросНоменклатура.УстановитьПараметр("СвойствоПроизводитель", ХарактеристикаПроизводитель);
ЗапросНоменклатура.УстановитьПараметр("ПроизводительСсылка", ПроизводительСсылка);
// Используем ВНУТРЕННЕЕ СОЕДИНЕНИЕ для проверки дополнительного реквизита
ЗапросНоменклатура.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура.ДополнительныеРеквизиты КАК НоменклатураДополнительныеРеквизиты
| ПО НоменклатураДополнительныеРеквизиты.Ссылка = Номенклатура.Ссылка
|ГДЕ
| Номенклатура.Артикул = &Артикул
| И НЕ Номенклатура.ПометкаУдаления
| И НоменклатураДополнительныеРеквизиты.Свойство = &СвойствоПроизводитель
| И НоменклатураДополнительныеРеквизиты.Значение = &ПроизводительСсылка";
ВыборкаНоменклатуры = ЗапросНоменклатура.Run().Select();
Если ВыборкаНоменклатуры.Next() Тогда
// Товар с таким артикулом и производителем уже есть в базе
НоменклатураСсылка = ВыборкаНоменклатуры.Ссылка;
Иначе
// Шаг 3. Создаем новую карточку товара, если поиск не дал результатов
НоменклатураОбъект = Справочники.Номенклатура.СоздатьЭлемент();
НоменклатураОбъект.ТипНоменклатуры = Перечисления.ТипыНоменклатуры.Товар;
НоменклатураОбъект.ВидНоменклатуры = ВидНоменклатуры;
НоменклатураОбъект.Качество = Перечисления.ГрадацииКачества.Новый;
НоменклатураОбъект.СтавкаНДС = Перечисления.СтавкиНДС.БезНДС;
ЗаполнитьЗначенияСвойств(НоменклатураОбъект, ВидНоменклатуры, , "Ссылка, Владелец, ПометкаУдаления, ВерсияДанных, Предопределенный, ЭтоГруппа, Родитель, Описание, Наименование, Код, ИмяПредопределенныхДанных");
НоменклатураОбъект.Наименование = Наименование;
НоменклатураОбъект.НаименованиеПолное = Наименование;
НоменклатураОбъект.Артикул = Артикул;
НоменклатураОбъект.Родитель = РодительСсылка;
// Заполняем дополнительный реквизит производителя
Если ЗначениеЗаполнено(ПроизводительСсылка) Тогда
ЗаписьПроизводитель = НоменклатураОбъект.ДополнительныеРеквизиты.Добавить();
ЗаписьПроизводитель.Значение = ПроизводительСсылка;
ЗаписьПроизводитель.Свойство = ХарактеристикаПроизводитель;
КонецЕсли;
Попытка
НоменклатураОбъект.Записать();
НоменклатураСсылка = НоменклатураОбъект.Ссылка;
Исключение
Сообщить("Ошибка при записи номенклатуры: " + ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
Возврат НоменклатураСсылка;
КонецФункции
Данный вариант является наиболее надежным и архитектурно верным: он исключает появление неконтролируемых дублей номенклатуры при повторном запуске обработки и при этом позволяет безболезненно хранить в базе товары с одинаковыми артикулами от разных производителей.
В ходе обсуждения на форумах часто советуют использовать конструкцию ОбменДанными.Загрузка = Истина. Выясним подробно, для чего она нужна, куда ее помещать в коде и какие опасности она в себе таит.
Эта директива устанавливается непосредственно для создаваемого объекта перед выполнением метода Записать(). Рассмотрим пример внедрения этой строки:
НоменклатураОбъект = Справочники.Номенклатура.СоздатьЭлемент();
// ... [Заполнение реквизитов объекта] ...
// Устанавливаем признак загрузки данных
НоменклатураОбъект.ОбменДанными.Загрузка = Истина;
Попытка
НоменклатураОбъект.Записать();
Исключение
Сообщить(ОписаниеОшибки());
КонецПопытки;
Что дает этот флаг:
ПередЗаписью и ПриЗаписи (как в модуле объекта, так и в подписках на события). Если в вашей системе настроен программный запрет на создание дублирующихся артикулов, этот флаг позволит обойти его.В чем заключаются риски (предупреждение из темы форума):
Обход стандартных механизмов проверки может нарушить логическую целостность вашей базы данных. При установленном флаге Загрузка = Истина система может не рассчитать автоматически хэш-коды для быстрого поиска, не заполнить обязательные скрытые реквизиты или пропустить генерацию рабочих наименований по шаблонам вида номенклатуры. В будущем это может привести к непредсказуемым ошибкам, например, когда менеджеры не смогут провести документ реализации с этой номенклатурой из-за незаполненных скрытых полей или некорректных признаков учета (учет по характеристикам, сериям и т.д.).
Прежде чем вносить изменения в программный код обработки или конфигурации, проанализируем возможности стандартного функционала 1С. В типовых решениях (УТ, ERP, КА) проверка уникальности артикулов зачастую вынесена в пользовательские настройки.
Для каждого Вида номенклатуры можно гибко настроить контроль заполнения и уникальности реквизитов. Если в настройках конкретного вида номенклатуры включен «Контроль уникальности по сочетанию реквизитов (например, артикул + производитель)», платформа самостоятельно будет следить за уникальностью при записи и не выдаст ошибку, если артикулы совпадают у разных производителей. Обязательно проверьте эти настройки в интерфейсе вашей программы перед доработкой кода.
Рассмотрим ситуацию с точки зрения методологии учета. Создание множества дублирующих карточек в справочнике Номенклатура только ради разделения товаров по брендам поставщиков может усложнить оперативную работу менеджеров, инвентаризацию и складской учет.
Если товары по своим физическим характеристикам и назначению идентичны, а различия заключаются лишь в наименованиях и артикулах у разных поставщиков, в 1С предусмотрен встроенный и гораздо более эффективный механизм — справочник «Номенклатура контрагентов» (в некоторых конфигурациях — «Номенклатура партнеров»):
Номенклатура вы ведете одну чистую и уникальную карточку товара, которую используют ваши кладовщики и менеджеры по продажам.Номенклатура контрагентов для каждого отдельного поставщика или производителя создается запись с его собственным артикулом и наименованием. Эта запись жестко связывается с вашей единой позицией номенклатуры.Использование этого механизма позволяет сохранить базу данных в чистоте, избежать дублирования позиций на складах и одновременно с этим корректно обрабатывать различные артикулы внешних поставщиков при импорте.