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