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