Как программно присвоить значение дополнительному реквизиту типа «Строка» в 1С:ERP?

Программист 1С v8.3 (Управляемые формы) 1С:ERP Управление предприятием Бухгалтерский учет
← На главную

Мы сталкиваемся с распространенной задачей в 1С:ERP — необходимостью программно изменить значение дополнительного реквизита объекта, который был создан через механизм "Дополнительные реквизиты и сведения". Прямое присвоение значения, например Объект.ИмяРеквизита = Значение;, не работает для таких реквизитов, поскольку они хранятся в отдельных регистрах сведений, а не напрямую в структуре объекта. Рассмотрим, как правильно выполнить эту операцию, опираясь на опыт разработчиков и типовые механизмы платформы 1С.

Ключевой механизм: Функция УправлениеСвойствами.ЗаписатьСвойстваУОбъекта

Для работы с дополнительными реквизитами в 1С:ERP (и других конфигурациях, построенных на Библиотеке Стандартных Подсистем — БСП) используется функциональность подсистемы "УправлениеСвойствами". Главной функцией для записи значений является УправлениеСвойствами.ЗаписатьСвойстваУОбъекта. Эта функция позволяет установить одно или несколько значений дополнительных реквизитов для указанного объекта.

Разберем ее использование подробнее, чтобы понять все нюансы и избежать распространенных ошибок.

Подготовка данных для записи

Функция УправлениеСвойствами.ЗаписатьСвойстваУОбъекта принимает два основных параметра:

  1. Первый параметр — это ссылка на объект, для которого мы хотим установить дополнительные реквизиты (например, СправочникСсылка.ОбъектыЭксплуатации). Важно передавать именно ссылку, а не сам объект, полученный через ПолучитьОбъект().
  2. Второй параметр — это таблица значений, содержащая список дополнительных реквизитов и их новых значений. Эта таблица значений должна иметь две колонки: "Свойство" и "Значение".

Давайте подробнее рассмотрим структуру этой таблицы:

  1. Колонка "Свойство": Должна содержать ссылку на элемент плана видов характеристик ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения, который соответствует нашему дополнительному реквизиту. Получить такую ссылку можно, например, по наименованию реквизита, используя метод НайтиПоНаименованию(). Необходимо убедиться, что наименование дополнительного реквизита указано точно, с учетом регистра и пробелов, иначе функция вернет Неопределено.
  2. Колонка "Значение": Должна содержать значение, которое мы хотим присвоить реквизиту. Тип значения должен строго соответствовать типу дополнительного реквизита. В нашем случае, если реквизит имеет тип "Строка", значение также должно быть строкой (даже если это числовое значение, например, "0").

Посмотрим на пример подготовки такой таблицы значений для установки строкового значения "0" в дополнительный реквизит с наименованием "При переходе на ФСБУ ост =":


// Создаем новую ТаблицуЗначений для хранения свойств
ТЗСвойств = Новый ТаблицаЗначений;

// Добавляем колонку "Свойство" с типом ссылки на дополнительный реквизит
ТЗСвойств.Колонки.Добавить("Свойство", Новый ОписаниеТипов("ПланВидовХарактеристикСсылка.ДополнительныеРеквизитыИСведения"));

// Добавляем колонку "Значение" для хранения значений реквизитов
ТЗСвойств.Колонки.Добавить("Значение");

// Определяем ссылку на наш дополнительный реквизит по его наименованию.
// Важно: наименование должно точно совпадать с тем, как реквизит создан в системе.
СсылкаНаСвойство = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("При переходе на ФСБУ ост =");

// Проверяем, что реквизит найден, чтобы избежать ошибок
Если СсылкаНаСвойство = Неопределено Тогда
    Сообщить("Дополнительный реквизит 'При переходе на ФСБУ ост =' не найден. Проверьте наименование и настройки.");
    Возврат; // Или выполните другую обработку ошибки
КонецЕсли;

// Добавляем новую строку в нашу ТаблицуЗначений
НоваяСтрока = ТЗСвойств.Добавить();

// Присваиваем колонке "Свойство" найденную ссылку на дополнительный реквизит
НоваяСтрока.Свойство = СсылкаНаСвойство;

// Присваиваем колонке "Значение" требуемое строковое значение "0"
НоваяСтрока.Значение = "0";

Эта подготовленная таблица ТЗСвойств теперь готова для передачи в функцию УправлениеСвойствами.ЗаписатьСвойстваУОбъекта().

Типичная ошибка: "Операция не может быть выполнена из-за несоответствия версии..."

Одна из наиболее частых ошибок, с которой сталкиваются разработчики при работе с УправлениеСвойствами.ЗаписатьСвойстваУОбъекта, — это ошибка "Операция не может быть выполнена из-за несоответствия версии или отсутствия записи базы данных" (ОшибкаХранимыхДанных). Как было показано в обсуждении на форуме, эта ошибка возникает, если мы пытаемся вызвать метод Записать() для объекта после того, как его дополнительные реквизиты уже были изменены и записаны функцией УправлениеСвойствами.ЗаписатьСвойстваУОбъекта().

Выясним причину этой ошибки: Функция УправлениеСвойствами.ЗаписатьСвойстваУОбъекта() самостоятельно записывает все изменения в базу данных, затрагивая при этом и сам объект (например, увеличивая его версию). Это означает, что после ее вызова объект уже является актуальным в базе данных. Попытка повторно вызвать Объект.Записать() для того же объекта, который, возможно, был получен ранее в режиме редактирования (через ПолучитьОбъект()), приводит к конфликту версий или попытке записать уже актуальные данные. Система расценивает это как несоответствие данных или попытку изменить устаревшую версию, что приводит к ошибке ОшибкаХранимыхДанных.

Решение проблемы: Никогда не вызывайте Объект.Записать() сразу после успешного вызова УправлениеСвойствами.ЗаписатьСвойстваУОбъекта() для того же объекта. Если вам нужно изменить другие реквизиты объекта, не являющиеся дополнительными, сначала измените их и вызовите Объект.Записать(), а затем уже работайте с дополнительными реквизитами через УправлениеСвойствами.ЗаписатьСвойстваУОбъекта(). Или наоборот, если дополнительные реквизиты не затрагивают другие изменения объекта, просто полагайтесь на автоматическую запись функцией БСП.

Неоптимальное получение объекта

Еще один момент, который следует учесть для оптимизации и правильности кода, — это получение объекта. В исходном коде использовалась конструкция ОбъектОС = Справочники.ОбъектыЭксплуатации.НайтиПоРеквизиту("ИнвентарныйНомер", Выборка.ИнвентарныйНомер); СтрокаОС = ОбъектОС.ПолучитьОбъект;. Если для записи дополнительных реквизитов нам нужна только ссылка на объект, то вызов ПолучитьОбъект() является излишним и несет потенциальные риски:

  1. Непроизводительность: ПолучитьОбъект() создает экземпляр объекта в памяти, что потребляет ресурсы, хотя для работы с дополнительными реквизитами достаточно только ссылки.
  2. Блокировки: При получении объекта для изменения (даже если мы его не записываем, но сам факт получения объекта) могут устанавливаться блокировки на запись, что может помешать другим пользователям или процессам работать с этим же объектом.

Решение: Передавайте в функцию УправлениеСвойствами.ЗаписатьСвойстваУОбъекта() напрямую ссылку на объект. В нашем запросе мы можем сразу выбирать ссылку на объект, а не только его инвентарный номер.

Пошаговое решение проблемы с примером кода

Теперь, учитывая все вышеизложенное, перейдем к исправлению и оптимизации предложенного кода из сообщения 4. Нашей задачей является найти объекты эксплуатации по инвентарному номеру и установить им дополнительный реквизит "При переходе на ФСБУ ост =" в значение "0".

  1. Получаем ссылки на объекты эксплуатации: Изменяем запрос так, чтобы он возвращал непосредственно ссылки на объекты Справочник.ОбъектыЭксплуатации.
  2. Подготавливаем таблицу значений с дополнительными реквизитами: Как мы рассмотрели ранее, создаем ТаблицаЗначений с колонками "Свойство" и "Значение", заполняем ее ссылкой на нужный дополнительный реквизит и его значением.
  3. Итерируемся по результатам запроса: Для каждой найденной ссылки на объект вызываем УправлениеСвойствами.ЗаписатьСвойстваУОбъекта(), передавая ссылку на объект и подготовленную таблицу свойств.
  4. Избегаем повторной записи: Важно: не вызываем СтрокаОС.Записать(); после записи свойств функцией УправлениеСвойствами.ЗаписатьСвойстваУОбъекта().

Вот как будет выглядеть исправленный и рабочий код:


&НаСервере
Процедура ЗаписатьЗначениеФСБУ()

    // 1. Создаем запрос для получения ссылок на объекты эксплуатации
    // Изменяем запрос, чтобы он выбирал СсылкуОбъекта, а не только ИнвентарныйНомер
    Запрос = Новый Запрос;
    Запрос.Текст =
        "ВЫБРАТЬ
        |    ОбъектыЭксплуатации.Ссылка КАК СсылкаОбъекта,
        |    ОбъектыЭксплуатации.ИнвентарныйНомер КАК ИнвентарныйНомер
        |ИЗ
        |    Справочник.ОбъектыЭксплуатации КАК ОбъектыЭксплуатации
        |ГДЕ
        |    ОбъектыЭксплуатации.ИнвентарныйНомер В(&МассивСтрок)";

    // Формируем массив инвентарных номеров из ТаблицыТоваров
    МассивСтрок = Новый Массив;
    Для Каждого Строки ИЗ ТаблицаТоваров Цикл
        МассивСтрок.Добавить(Строки.ИнвентарныйНомер);
    КонецЦикла;

    // Устанавливаем параметр запроса
    Запрос.УстановитьПараметр("МассивСтрок", МассивСтрок);

    // Выполняем запрос
    РезультатЗапроса = Запрос.Выполнить();

    // Проверяем, есть ли результаты
    Если РезультатЗапроса.Пустой() Тогда
        Сообщить("Не найдено объектов с указанными инвентарными номерами.");
        Возврат;
    КонецЕсли;

    Выборка = РезультатЗапроса.Выбрать();

    // 2. Подготавливаем ТаблицуЗначений для записи свойств
    ТЗСвойств = Новый ТаблицаЗначений;
    ТЗСвойств.Колонки.Добавить("Свойство", Новый ОписаниеТипов("ПланВидовХарактеристикСсылка.ДополнительныеРеквизитыИСведения"));
    ТЗСвойств.Колонки.Добавить("Значение");

    // Определяем ссылку на дополнительный реквизит по наименованию
    СсылкаНаСвойство = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("При переходе на ФСБУ ост =");

    Если СсылкаНаСвойство = Неопределено Тогда
        Сообщить("Дополнительный реквизит 'При переходе на ФСБУ ост =' не найден. Проверьте наименование и настройки.");
        Возврат;
    КонецЕсли;

    // Добавляем строку в таблицу свойств с нужным значением
    НовСтр = ТЗСвойств.Добавить();
    НовСтр.Свойство = СсылкаНаСвойство;
    НовСтр.Значение = "0"; // Значение "0" как строка, согласно типу реквизита

    // 3. Перебираем выборку и записываем свойства для каждого объекта
    Пока Выборка.Следующий() Цикл
        // Передаем ссылку на объект напрямую, избегая ПолучитьОбъект()
        УправлениеСвойствами.ЗаписатьСвойстваУОбъекта(Выборка.СсылкаОбъекта, ТЗСвойств);
        // СтрокаОС.Записать; -- Эту строку удаляем! Она приводит к ошибке ОшибкаХранимыхДанных
    КонецЦикла;

    Сообщить("Значения дополнительных реквизитов успешно обновлены для всех найденных объектов.");

КонецПроцедуры

Чтение дополнительных реквизитов

Для полноты картины, рассмотрим также, как можно прочитать значения дополнительных реквизитов. Для этого служит функция УправлениеСвойствами.ПолучитьСвойстваОбъекта(). Она принимает на вход ссылку на объект и возвращает таблицу значений с его дополнительными реквизитами и их значениями.


// Предположим, у нас есть ссылка на объект, например, из справочника ОбъектыЭксплуатации
ОбъектСсылка = Справочники.ОбъектыЭксплуатации.НайтиПоНаименованию("Наш_Объект_Эксплуатации_123"); 

Если ОбъектСсылка <> Неопределено Тогда
    // Получаем таблицу значений со всеми дополнительными свойствами объекта
    ТаблицаСвойствОбъекта = УправлениеСвойствами.ПолучитьСвойстваОбъекта(ОбъектСсылка);
    
    // Перебираем полученные свойства, чтобы найти нужное
    Для Каждого СтрокаСвойства Из ТаблицаСвойствОбъекта Цикл
        // Сравниваем наименование свойства (реквизита)
        Если СтрокаСвойства.Свойство.Наименование = "При переходе на ФСБУ ост =" Тогда
            Сообщить("Значение реквизита 'При переходе на ФСБУ ост =' для объекта '" 
                     + ОбъектСсылка + "': " + СтрокаСвойства.Значение);
            Прервать; // Если нашли нужное, можно выйти из цикла
        КонецЕсли;
    КонецЦикла;
Иначе
    Сообщить("Объект не найден.");
КонецЕсли;

Дополнительные рекомендации

При работе с большим количеством объектов или свойств, а также для обеспечения стабильности и производительности, рекомендуем учитывать следующие моменты:

  1. Транзакции: Рекомендуется выполнять такие операции (особенно массовые обновления) в транзакции. Это гарантирует, что либо все изменения будут применены успешно, либо ни одно из них. Это важно для поддержания целостности данных. Для этого используйте НачатьТранзакцию(), ЗафиксироватьТранзакцию() и ОтменитьТранзакцию().
  2. Производительность: Если необходимо массово обновить дополнительные реквизиты для очень большого количества объектов (тысячи или десятки тысяч), УправлениеСвойствами.ЗаписатьСвойстваУОбъекта() сама по себе достаточно оптимизирована. Однако, если возникают проблемы с производительностью, стоит проанализировать общую архитектуру решения и, возможно, использовать фоновые задания или пакетную обработку данных.
  3. Проверка наименования: Всегда тщательно проверяйте, что наименование дополнительного реквизита, которое вы передаете в ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию(), точно соответствует наименованию, указанному при его создании в конфигурации, включая пробелы, регистр букв и другие символы. Малейшее несоответствие приведет к тому, что реквизит не будет найден, и функция вернет Неопределено. Добавление проверки на Неопределено, как показано в примере, является хорошей практикой.
  4. Типы значений: Убедитесь, что передаваемое значение точно соответствует типу данных дополнительного реквизита. Например, если реквизит имеет тип "Булево", передавайте Истина или Ложь. Если тип "Число", передавайте числовое значение, и так далее. Несоответствие типов также может привести к ошибкам.

Следуя этим рекомендациям и используя функцию УправлениеСвойствами.ЗаписатьСвойстваУОбъекта(), вы сможете корректно и эффективно управлять дополнительными реквизитами в вашей конфигурации 1С:ERP, избегая распространенных ошибок и обеспечивая надежность работы системы.

← На главную