Как программно записать дополнительные реквизиты в 1С и избежать ошибки «Данные сохранены другим пользователем»

Программист 1С v8.3 (Управляемые формы)
← На главную

Работа с дополнительными реквизитами и сведениями — одна из самых частых задач при адаптации типовых конфигураций 1С (ERP, УТ 11, КА 2, БП 3.0). Несмотря на кажущуюся простоту табличной части ДополнительныеРеквизиты, программисты часто сталкиваются с тем, что данные не сохраняются, либо форма выдает ошибку о модификации объекта другим пользователем. В этой статье мы подробно разберем механизмы работы со свойствами, проанализируем типичные ошибки и рассмотрим правильные способы программной записи данных.

Различие между дополнительными реквизитами и сведениями

Прежде чем переходить к коду, крайне важно четко разделять два механизма, которые в интерфейсе выглядят похоже, но технически реализованы абсолютно по-разному. Проанализируем эти различия:

  1. Дополнительные реквизиты — это данные, которые хранятся внутри самого объекта (справочника или документа). В метаданных объекта для этого предусмотрена табличная часть ДополнительныеРеквизиты. Чтобы изменить такой реквизит, нужно получить объект, модифицировать его табличную часть и вызвать метод Записать().
  2. Дополнительные сведения — это данные, которые хранятся «снаружи» объекта. Они записываются в независимый регистр сведений ЗначенияСвойствОбъектов. Для их записи сам объект (например, карточку контрагента) изменять не требуется, достаточно знать его ссылку.

Если мы попытаемся записать дополнительный реквизит через менеджер записи регистра сведений (как для сведений), мы не увидим результата в объекте. И наоборот.

Способ 1. Программное изменение через табличную часть объекта

Рассмотрим ситуацию, когда нам необходимо записать дополнительный реквизит, зная ссылку на объект. Этот метод подходит для регламентных заданий или внешних обработок, где нет открытой формы объекта. Выясним причину, почему стандартное добавление строки может не работать.

Для корректной записи выполним следующие шаги:


Функция ЗаписатьЗначениеДопРеквизита(СсылкаНаОбъект, СвойствоСсылка, НовоеЗначение)
    
    // Получаем объект из ссылки для редактирования
    ОбъектДляЗаписи = СсылкаНаОбъект.ПолучитьОбъект();
    
    // Ищем, есть ли уже такой реквизит в табличной части
    ПараметрыОтбора = Новый Структура("Свойство", СвойствоСсылка);
    НайденныеСтроки = ОбъектДляЗаписи.ДополнительныеРеквизиты.НайтиСтроки(ПараметрыОтбора);
    
    Если НайденныеСтроки.Количество() = 0 Тогда
        // Если реквизита нет, добавляем новую строку
        НоваяСтрока = ОбъектДляЗаписи.ДополнительныеРеквизиты.Добавить();
        НоваяСтрока.Свойство = СвойствоСсылка;
        НоваяСтрока.Значение = НовоеЗначение;
    Иначе
        // Если нашли — обновляем значение в первой найденной строке
        НайденныеСтроки[0].Значение = НовоеЗначение;
    КонецЕсли;
    
    // Пытаемся записать объект
    Попытка
        ОбъектДляЗаписи.Записать();
        Возврат Истина;
    Исключение
        Инфо = ИнформацияОбОшибке();
        ЗаписьЖурналаРегистрации("ПрограммнаяЗаписьРеквизита", УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(Инфо));
        Возврат Ложь;
    КонецПопытки;
    
КонецФункции

Важный нюанс: при использовании этого метода всегда проверяйте тип значения. Если дополнительный реквизит имеет тип «Дополнительное значение», то в поле Значение нужно передавать не строку с текстом, а ссылку на элемент справочника ЗначенияСвойствОбъектов. Если передать просто текст, 1С при записи очистит поле, так как типы не совпадут.

Проблема: Конфликт версий на форме («Данные сохранены другим пользователем»)

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

Когда пользователь решит нажать стандартную кнопку «Записать» на форме, система сравнит версию объекта в памяти формы и версию в базе данных. Поскольку вы уже изменили базу программно, версии не совпадут, и 1С выдаст ошибку: «Данные сохранены другим пользователем или другим сеансом».

Чтобы избежать этого при работе на форме, следуйте правилам:

  1. Если нужно просто заполнить реквизит, чтобы пользователь сам его сохранил — работайте напрямую с реквизитом Объект (который является типом ДанныеФормыСтруктура). Не вызывайте метод Записать().
  2. Если запись в базу обязательна прямо сейчас, после вызова метода записи объекта необходимо обновить данные на форме, вызвав ЭтаФорма.Прочитать().

Способ 2. Использование типовых функций БСП

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

Рассмотрим пример кода:


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

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

// Используем типовой метод БСП
УправлениеСвойствами.ЗаписатьСвойстваУОбъекта(СсылкаНаКонтрагента, ТаблицаСвойств);

Этот метод предпочтительнее, так как он учитывает все внутренние контроли конфигурации и правильно работает с регистрами сведений для дополнительных сведений.

Как правильно искать дополнительный реквизит

Посмотрим на пример поиска свойства: НайтиПоНаименованию("Код ОПФ"). Использовать поиск по наименованию крайне опасно, так как в системе может быть несколько реквизитов с одинаковым названием для разных справочников (например, «Вес» для Номенклатуры и «Вес» для Спецификации).

Правильнее всего искать реквизит с учетом его владельца — Набора свойств. Посмотрим на пример более надежного поиска:


Функция НайтиСвойствоПоИмениИНабору(ИмяСвойства, ИмяНабора)
    // Имя набора обычно соответствует метаданным, например "Справочник_Контрагенты"
    НаборСвойств = Справочники.НаборыДополнительныхРеквизитовИСведений.НайтиПоНаименованию(ИмяНабора);
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Наборы.Свойство КАК Ссылка
        |ИЗ
        |   ПланВидовХарактеристик.ДополнительныеРеквизитыИСведения КАК Свойства
        |       ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.НаборыДополнительныхРеквизитовИСведений.ДополнительныеРеквизиты КАК Наборы
        |       ПО Свойства.Ссылка = Наборы.Свойство
        |ГДЕ
        |   Свойства.Наименование = &Наименование
        |   И Наборы.Ссылка = &Набор";
    
    Запрос.УстановитьПараметр("Наименование", ИмяСвойства);
    Запрос.УстановитьПараметр("Набор", НаборСвойств);
    
    Результат = Запрос.Выполнить();
    Если Не Результат.Пустой() Тогда
        Выборка = Результат.Выбрать();
        Выборка.Следующий();
        Возврат Выборка.Ссылка;
    КонецЕсли;
    
    Возврат ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.ПустаяСсылка();
КонецФункции

Резюме и рекомендации

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

Соблюдение этих правил позволит вам корректно расширять функциональность типовых решений и избегать трудноуловимых ошибок при сохранении данных.

← На главную