В современных конфигурациях 1С, построенных на базе Библиотеки стандартных подсистем (БСП), механизм дополнительных реквизитов и сведений является стандартом де-факто. Однако для начинающих разработчиков программное взаимодействие с этими данными часто становится камнем преткновения. В этой статье мы подробно разберем, как правильно изменять дополнительные реквизиты, почему нельзя просто присвоить значение строкой и как оптимизировать этот процесс для массовой обработки документов и справочников.
Рассмотрим ситуацию, с которой столкнулся автор темы: необходимо перебрать документы, найти в них серии номенклатуры и заполнить у этих серий дополнительный реквизит «Цена». Первой и самой распространенной ошибкой является попытка присвоить свойству текстовое значение или найти его простым поиском по наименованию. Проанализируем, почему это вызывает сложности.
Дополнительные реквизиты хранятся в табличной части объекта, которая называется ДополнительныеРеквизиты. Эта табличная часть имеет две ключевые колонки: Свойство (ссылка на План видов характеристик ДополнительныеРеквизитыИСведения) и Значение (составной тип данных). Когда мы пытаемся написать НовоеСвойство.Свойство = "Цена", система выдает ошибку, так как ожидает ссылку, а не строку.
Выясним причину, по которой НайтиПоНаименованию("Цена") может возвращать пустое значение или не тот элемент. В БСП для одного и того же заголовка (например, «Цена») может существовать несколько записей в справочнике свойств, привязанных к разным наборам (для контрагентов, для номенклатуры, для серий). Кроме того, существует понятие «Имя» (внутренний идентификатор), которое может выглядеть как Цена_e71e1673.... Именно правильный поиск ссылки на свойство является залогом успеха.
Если нам нужно обновить реквизит у конкретного объекта, который мы уже получили для редактирования, мы можем использовать стандартные методы работы с табличными частями. Рассмотрим пример функции, которая проверяет наличие свойства и обновляет его значение или добавляет новую строку, если свойства еще нет.
Проанализируем следующий код:
&НаСервере
Процедура ЗаписатьЗначениеДопРеквизита(СсылкаНаОбъект, СвойствоСсылка, НовоеЗначение)
// Получаем объект для редактирования
Объект = СсылкаНаОбъект.ПолучитьОбъект();
// Пытаемся найти строку с нужным свойством в ТЧ ДополнительныеРеквизиты
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("Свойство", СвойствоСсылка);
НайденныеСтроки = Объект.ДополнительныеРеквизиты.НайтиСтроки(ПараметрыОтбора);
Если НайденныеСтроки.Количество() = 0 Тогда
// Если свойства нет, добавляем новую строку
НоваяСтрока = Объект.ДополнительныеРеквизиты.Добавить();
НоваяСтрока.Свойство = СвойствоСсылка;
НоваяСтрока.Значение = НовоеЗначение;
Иначе
// Если свойство найдено, обновляем значение в первой найденной строке
НайденныеСтроки[0].Значение = НовоеЗначение;
КонецЕсли;
// Фиксируем изменения в базе данных
Объект.Записать();
КонецПроцедуры
Этот метод хорош своей прозрачностью, но требует осторожности при работе в циклах, так как каждый вызов Записать() инициирует полноценную транзакцию записи объекта со всеми проверками.
Более профессиональным и надежным способом является использование встроенных функций БСП. Общий модуль УправлениеСвойствами содержит метод ЗаписатьСвойстваУОбъекта, который берет на себя все сложности по поиску, добавлению и обновлению данных, включая работу с дополнительными сведениями (которые хранятся не в самом объекте, а в отдельном регистре).
Разберем по шагам, как подготовить данные для этого метода. Нам потребуется создать ТаблицуЗначений с определенной структурой.
// 1. Подготавливаем таблицу значений со свойствами
ТаблицаСвойств = Новый ТаблицаЗначений;
ТаблицаСвойств.Колонки.Добавить("Свойство", Новый ОписаниеТипов("ПланВидовХарактеристикСсылка.ДополнительныеРеквизитыИСведения"));
ТаблицаСвойств.Колонки.Добавить("Значение");
// 2. Ищем ссылку на нужное свойство.
// Важно: Поиск по наименованию может потребовать уточнения,
// например: "Цена (Серии номенклатуры)"
ИмяСвойства = "Цена";
СвойствоСсылка = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию(ИмяСвойства);
Если НЕ СвойствоСсылка.Пустая() Тогда
НоваяЗапись = ТаблицаСвойств.Добавить();
НоваяЗапись.Свойство = СвойствоСсылка;
НоваяЗапись.Значение = 1500; // Наша цена
// 3. Вызываем программный интерфейс БСП для записи
// Передаем ССЫЛКУ на объект и подготовленную таблицу
УправлениеСвойствами.ЗаписатьСвойстваУОбъекта(ОбъектСсылка, ТаблицаСвойств);
КонецЕсли;
Важный момент: использование этого метода гарантирует, что вы не создадите дублей в табличной части и корректно обработаете типы данных. Однако помните, что метод ЗаписатьСвойстваУОбъекта внутри себя также вызывает ПолучитьОбъект().Записать(), поэтому при массовой обработке тысяч документов это может быть медленно.
Если перед нами стоит задача перебрать все документы поступления и обновить серии, как в примере автора сообщения, мы должны минимизировать количество обращений к серверу БД. Рассмотрим типичные ошибки оптимизации.
Во-первых, избегайте вызовов типа НайтиДокументПоНомеруНаСервере внутри цикла. Если вы уже используете Выборка = Документы.ПоступлениеТоваров.Выбрать(), то у вас уже есть доступ к данным. Однако Выбрать() возвращает выборку, а не объект. Чтобы изменить данные, вам нужно получить объект: ДокОбъект = Выборка.ПолучитьОбъект().
Проанализируем ситуацию с сериями. Серия — это справочник. Если одна и та же серия используется в разных строках или разных документах, нет смысла записывать её объект каждый раз. Рассмотрим алгоритм эффективной обработки:
Соответствие (кэш), где ключом будет ссылка на серию, а значением — уже рассчитанная цена или флаг обработки.Посмотрим на пример кода с учетом этих рекомендаций:
ВыборкаДокументов = Документы.ПоступлениеТоваров.Выбрать(ДатаНачала, ДатаКонца);
ОбработанныеСерии = Новый Соответствие;
Пока ВыборкаДокументов.Следующий() Цикл
ДокументОбъект = ВыборкаДокументов.ПолучитьОбъект();
Изменено = Ложь;
Для Каждого СтрокаТЧ Из ДокументОбъект.Товары Цикл
СерияСсылка = СтрокаТЧ.Серия;
Если СерияСсылка.Пустая() Тогда Продолжить; КонецЕсли;
// Проверяем, не обрабатывали ли мы эту серию в этом запуске
Если ОбработанныеСерии[СерияСсылка] = Неопределено Тогда
СерияОбъект = СерияСсылка.ПолучитьОбъект();
// Обновляем наименование по шаблону
Шаблон = Строка(СерияОбъект.Номер) + " цена " + Строка(СтрокаТЧ.Цена);
СерияОбъект.Наименование = Шаблон;
// Обновляем доп. реквизит "Цена" напрямую в ТЧ серии
// (Предположим, мы заранее нашли СвойствоСсылкаЦена)
СтрокаДопРеквизита = СерияОбъект.ДополнительныеРеквизиты.Найти(СвойствоСсылкаЦена, "Свойство");
Если СтрокаДопРеквизита = Неопределено Тогда
СтрокаДопРеквизита = СерияОбъект.ДополнительныеРеквизиты.Добавить();
СтрокаДопРеквизита.Свойство = СвойствоСсылкаЦена;
КонецЕсли;
СтрокаДопРеквизита.Значение = СтрокаТЧ.Цена;
СерияОбъект.Записать();
ОбработанныеСерии.Вставить(СерияСсылка, Истина);
КонецЕсли;
// Если нужно обновить что-то в самом документе (например, СтатусУказанияПартий)
СтрокаТЧ.СтатусУказанияПартий = 2;
Изменено = Истина;
КонецЦикла;
Если Изменено Тогда
ДокументОбъект.Записать();
КонецЕсли;
КонецЦикла;
Часто программисты сталкиваются с тем, что код отработал, данные в базе изменились, но в открытой форме документа пользователь видит старую информацию. Это происходит из-за кэширования данных в управляемых формах.
Чтобы заставить форму обновиться, после программного изменения объекта рекомендуется использовать метод ОповеститьОбИзменении(Ссылка). Это пошлет сигнал всем открытым формам, отображающим данный объект, перечитать данные из базы. В случае массовой обработки это делать не обязательно, но для интерактивных обработок — крайне желательно.
Также стоит помнить про системное поле «Имя» в Плане видов характеристик. Если ваша конфигурация часто обновляется или вы переносите код между разными базами, лучше использовать поиск по уникальному идентификатору (GUID) свойства или по его уникальному имени, заданному разработчиком, так как обычное наименование пользователь может случайно изменить в режиме «1С:Предприятие».
Подводя итог, отметим, что наиболее стабильным методом работы является использование УправлениеСвойствами.ЗаписатьСвойстваУОбъекта, но для понимания сути процесса полезно знать и механизм прямого обращения к табличной части ДополнительныеРеквизиты. Сочетание этих знаний позволит вам писать эффективный и надежный код для автоматизации любых задач в среде 1С.