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