При разработке и автоматизации в среде 1С:Предприятие 8.3 одной из частых задач является пакетное или программное заполнение свойств объектов. В современных типовых конфигурациях (таких как 1С:Управление торговлей 11, 1С:ERP Управление предприятием 2, 1С:Комплексная автоматизация 2) свойства объектов делятся на дополнительные реквизиты и дополнительные сведения. Механизм их хранения реализован на базе Библиотеки стандартных подсистем (БСП) и имеет свои архитектурные особенности. Если задачу требуется решить без написания кода, можно использовать платформу для создания программных роботов и автоматизации действий.
Часто начинающие разработчики сталкиваются с тем, что написанный ими код выполняется без видимых ошибок, однако новые значения дополнительных реквизитов в карточке номенклатуры не сохраняются. В этой статье мы подробно разберем, почему возникает эта проблема, проанализируем типичные ошибки, рассмотрим классический вариант исправления кода через работу с табличной частью и изучим наиболее правильный, современный метод записи свойств с использованием штатного API БСП.
Давайте проанализируем исходный код, с которым столкнулся автор темы. В его обработчике выполнялся обход выборки номенклатуры, получался объект, выполнялся поиск дополнительного реквизита в табличной части, после чего происходило присвоение нового значения. Однако изменения не записывались в базу данных.
Выясним причину такого поведения системы, взглянув на проблемный участок кода:
Значение = Номенкл.ДополнительныеРеквизиты.Найти(ДопСвойство);
Если Значение = Неопределено Тогда
Значение = Строка(Номенкл.Артикул) + ".jpg";
КонецЕсли;
Номенкл.Записать();
В чем кроется главная ошибка? Метод Найти() для табличной части возвращает объект типа СтрокаТабличнойЧасти, если строка найдена, и значение Неопределено, если строка с указанным свойством отсутствует.
Когда условие Значение = Неопределено выполняется, разработчик пытается напрямую присвоить переменной Значение строковый тип: Значение = Строка(Номенкл.Артикул) + ".jpg". В этот момент локальная переменная Значение просто перезаписывается в памяти новым строковым значением. При этом сама табличная часть справочника никак не изменяется: в нее не добавляется новая строка, и в базу данных записывается абсолютно неизмененный объект номенклатуры.
Даже если бы строка была найдена, переменная Значение содержала бы всю строку табличной части целиком (объект СтрокаТабличнойЧасти), а не конкретное поле. Попытка перезаписать саму строку ТЧ простым присвоением строкового значения также привела бы к ошибке типов или отсутствию результата.
Для того чтобы успешно изменить или добавить дополнительный реквизит напрямую через объект, нам необходимо четко разделять понятия строки табличной части и непосредственно значения свойства в этой строке. Табличная часть ДополнительныеРеквизиты содержит две ключевые колонки: Свойство (ссылка на План видов характеристик ДополнительныеРеквизитыИСведения) и Значение (хранит само значение свойства).
Разберем по шагам правильный алгоритм прямого изменения табличной части:
Найти() табличной части ДополнительныеРеквизиты для поиска строки по колонке Свойство.Значение и перезаписываем его.Добавить(), после чего заполняем колонки Свойство и Значение.Посмотрим на пример практической реализации данного алгоритма:
&НаСервере
Процедура ОбновитьДопРеквизитНапрямую()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Артикул КАК Артикул
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ЭтоГруппа";
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
// Получаем свойство ОДИН РАЗ до начала цикла для экономии ресурсов СУБД
ДопСвойство = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("Код товара");
Если ДопСвойство.Пустая() Тогда
ВызватьИсключение "Не найден дополнительный реквизит ""Код товара""!";
КонецЕсли;
Пока Выборка.Следующий() Цикл
НоменклОбъект = Выборка.Ссылка.ПолучитьОбъект();
// Пытаемся найти уже существующую строку с этим свойством в ТЧ номенклатуры
СтрокаТЧ = НоменклОбъект.ДополнительныеРеквизиты.Найти(ДопСвойство, "Свойство");
НовоеЗначение = Строка(Выборка.Артикул) + ".jpg";
Если СтрокаТЧ <> Неопределено Тогда
// Если строка найдена, проверяем, изменилось ли значение, чтобы избежать лишней записи
Если СтрокаТЧ.Значение <> НовоеЗначение Тогда
СтрокаТЧ.Значение = НовоеЗначение;
НоменклОбъект.Записать();
КонецЕсли;
Иначе
// Если строки со свойством нет — добавляем новую строку
СтрокаТЧ = НоменклОбъект.ДополнительныеРеквизиты.Добавить();
СтрокаТЧ.Свойство = ДопСвойство;
СтрокаТЧ.Значение = НовоеЗначение;
НоменклОбъект.Записать();
КонецЕсли;
КонецЦикла;
КонецПроцедуры
В коде, который часто пишут новички, скрыты две серьезные проблемы производительности, способные заблокировать работу пользователей в реальной базе данных при обработке большого каталога товаров:
НайтиПоНаименованию() выполняет обращение к базе данных. Если у вас 50 000 товаров, платформа сделает 50 000 одинаковых запросов к таблице планов видов характеристик. Всегда получайте ссылки на метаданные и настройки до начала цикла обхода выборки.Записать() запускает тяжелые транзакционные процессы: проверку прав доступа, запись истории изменений (версионирование объектов), регистрацию изменений на узлах планов обмена для интеграций и выполнение кода в подписках на события (например, обновление цен или пересчет остатков). Записывать объект нужно исключительно тогда, когда новое значение реквизита действительно отличается от старого.Прямая модификация табличных частей объектов через метод ПолучитьОбъект() и последующую перезапись крайне нежелательна в современных ERP-системах. БСП предоставляет разработчикам высокоуровневый, стандартизированный и безопасный инструмент для управления свойствами — общий модуль УправлениеСвойствами. Для быстрого изучения программного API и поиска готовых примеров вызовов можно использовать справочник по методам БСП.
Использование БСП API обладает рядом неоспоримых преимуществ:
ПолучитьОбъект() на стороне разработчика, БСП оптимизирует этот процесс под капотом.Рассмотрим, как правильно выполнить запись дополнительного реквизита с использованием методов БСП:
&НаСервере
Процедура ЗаписатьДопРеквизитЧерезБСП(СсылкаНаНоменклатуру, СвойствоКодТовара, НовоеЗначение)
// Создаем таблицу значений для передачи измененных свойств в БСП
ТаблицаСвойств = Новый ТаблицаЗначений;
ТаблицаСвойств.Колонки.Добавить("Свойство", Новый ОписаниеТипов("ПланВидовХарактеристикСсылка.ДополнительныеРеквизитыИСведения"));
ТаблицаСвойств.Колонки.Добавить("Значение");
// Заполняем строку новыми данными
НоваяСтрока = ТаблицаСвойств.Добавить();
НоваяСтрока.Свойство = СвойствоКодТовара;
НоваяСтрока.Значение = НовоеЗначение;
// Вызываем стандартный метод БСП для записи свойств у объекта
УправлениеСвойствами.ЗаписатьСвойстваУОбъекта(СсылкаНаНоменклатуру, ТаблицаСвойств);
КонецПроцедуры
В данном сценарии метод УправлениеСвойствами.ЗаписатьСвойстваУОбъекта принимает ссылку на целевой объект (номенклатуру) и таблицу с измененными свойствами. Система самостоятельно выполнит все необходимые проверки, обновит табличную часть и запишет объект наиболее оптимальным для СУБД способом.
В процессе обсуждений на форумах разработчики нередко путают две разные сущности: "Дополнительные реквизиты" и "Дополнительные сведения". Это принципиальная архитектурная разница, которую необходимо понимать для выбора правильного метода программирования:
Дополнительные реквизиты — это часть самого объекта. Они физически хранятся в табличной части ДополнительныеРеквизиты справочника или документа. Изменение такого реквизита приводит к изменению самого объекта (меняется версия объекта, дата его изменения, срабатывают бизнес-процессы и подписки на события изменения карточки).
Дополнительные сведения — это внешние данные по отношению к объекту. Они хранятся отдельно от номенклатуры в независимом регистре сведений РегистрСведений.ДополнительныеСведения. Изменение сведений не изменяет сам объект-владелец. Это позволяет добавлять или менять информацию по объектам даже в тех периодах, которые закрыты для редактирования пользователям.
Если перед вами стоит задача программно записать не реквизит, а Дополнительное сведение, код прямого обращения к табличной части работать не будет. В этом случае необходимо работать с набором записей регистра сведений. Рассмотрим пример записи дополнительного сведения:
&НаСервере
Процедура ЗаписатьДополнительноеСведение(ОбъектСсылка, СвойствоСведение, ЗначениеСведения)
НаборЗаписей = РегистрыСведений.ДополнительныеСведения.СоздатьНаборЗаписей();
// Устанавливаем жесткий отбор по объекту-владельцу и свойству
НаборЗаписей.Отбор.Объект.Установить(ОбъектСсылка);
НаборЗаписей.Отбор.Свойство.Установить(СвойствоСведение);
// Добавляем запись в набор
НоваяСтрока = НаборЗаписей.Добавить();
НоваяСтрока.Объект = ОбъектСсылка;
НоваяСтрока.Свойство = СвойствоСведение;
НоваяСтрока.Значение = ЗначениеСведения;
Попытка
НаборЗаписей.Записать(Истина); // Параметр Истина замещает существующие записи по отбору
Исключение
ЗаписьЖурналаРегистрации("Программное изменение свойств",
УровеньЖурналаРегистрации.Ошибка,,,
"Ошибка записи сведений для объекта: " + ОбъектСсылка + ". Описание: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
В практических задачах дополнительным реквизитам не всегда присваиваются простые типы данных вроде "Строка", "Число" или "Булево". Очень часто тип значения дополнительного реквизита настроен как "Дополнительное значение". В этом случае типом данных реквизита является ссылка на элемент подчиненного справочника Справочники.ЗначенияСвойствОбъектов.
Если вы попытаетесь записать обычную строку в реквизит с типом "Дополнительное значение", система либо очистит поле, либо выдаст ошибку несоответствия типов при интерактивном открытии карточки номенклатуры.
Проанализируем пример правильного поиска и создания нового значения для такого реквизита:
&НаСервере
Функция ПолучитьСсылкуНаДополнительноеЗначение(ДопСвойство, ТекстовоеЗначение)
// Ищем значение среди элементов, подчиненных нашему свойству-владельцу
СсылкаНаЗначение = Справочники.ЗначенияСвойствОбъектов.НайтиПоНаименованию(ТекстовоеЗначение, Истина,, ДопСвойство);
// Если значения с таким наименованием еще нет, создаем его программно
Если СсылкаНаЗначение.Пустая() Тогда
НовоеЗначениеОбъект = Справочники.ЗначенияСвойствОбъектов.СоздатьЭлемент();
НовоеЗначениеОбъект.Владелец = ДопСвойство;
НовоеЗначениеОбъект.Наименование = ТекстовоеЗначение;
Попытка
НовоеЗначениеОбъект.Записать();
СсылкаНаЗначение = НовоеЗначениеОбъект.Ссылка;
Исключение
ВызватьИсключение "Не удалось записать новое значение свойства: " + ОписаниеОшибки();
КонецПопытки;
КонецЕсли;
Возврат СсылкаНаЗначение;
КонецФункции
Используя данную функцию, вы сначала гарантированно получаете корректную ссылку на элемент справочника ЗначенияСвойствОбъектов, после чего записываете эту ссылку в колонку Значение табличной части ДополнительныеРеквизиты номенклатуры или передаете ее в параметры методов общего модуля БСП.
Резюмируя, при программной работе с дополнительными реквизитами в 1С всегда отдавайте предпочтение методам БСП. Если же вы используете прямое изменение табличной части объекта, обязательно оптимизируйте циклы поиска метаданных и контролируйте фактическое изменение данных перед выполнением метода записи.