Как правильно обновить реквизиты подчиненных элементов группы в управляемых формах 1С?

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

При разработке функционала в управляемом приложении 1С часто возникает задача: при изменении определенного реквизита в группе справочника автоматически распространить это новое значение на все подчиненные элементы. Однако начинающие разработчики нередко сталкиваются с ситуацией, когда при первом вызове обработчика данные не обновляются либо перезаписываются со старыми значениями. Разберем причины такого поведения и рассмотрим правильные варианты реализации этой задачи.

В чем причина ошибки при передаче данных из формы на сервер

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


&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
    ЗаменаЗначенийУЭлементовВгруппе(Объект.Ссылка, Объект.МойРекв);
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ЗаменаЗначенийУЭлементовВгруппе(РодительСсылка, НовоеЗначение)
    Выборка = Справочники.Номенклатура.Выбрать(РодительСсылка);
    Пока Выборка.Следующий() Цикл
        Если Выборка.ЭтоГруппа Тогда
            Продолжить;
        КонецЕсли;    
        Попытка
            ОбъектНом = Выборка.ПолучитьОбъект();
            // Ошибка: обращение к реквизиту ссылки из базы, а не к переданному значению
            ОбъектНом.МойРекв = РодительСсылка.МойРекв; 
            ОбъектНом.Записать();
        Исключение
            Сообщить("Ошибка: " + ОписаниеОшибки());
        КонецПопытки;    
    КонецЦикла;    
КонецПроцедуры

Выясним основные проблемы представленного выше алгоритма:

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

Вариант 1. Исправление логики при прямом вызове из формы

Рассмотрим, как скорректировать код для корректной передачи значения и обработки подчиненных элементов:


&НаКлиенте
Процедура МойРеквПриИзменении(Элемент)
    // Передаем ссылку на родительскую группу и актуальное значение из данных формы
    ЗаменаЗначенийУЭлементовВгруппе(Объект.Ссылка, Объект.МойРекв);
    // Оповещаем открытые списки об изменении данных
    ОповеститьОбИзменении(Тип("СправочникСсылка.Номенклатура"));
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ЗаменаЗначенийУЭлементовВгруппе(РодительСсылка, НовоеЗначение)
    Если Не ЗначениеЗаполнено(РодительСсылка) Тогда
        Возврат;
    КонецЕсли;

    Выборка = Справочники.Номенклатура.Выбрать(РодительСсылка);
    Пока Выборка.Следующий() Цикл
        Если Выборка.ЭтоГруппа Тогда
            Продолжить;
        КонецЕсли;

        Попытка
            ОбъектНом = Выборка.ПолучитьОбъект();
            ОбъектНом.МойРекв = НовоеЗначение;
            ОбъектНом.Записать();
        Исключение
            ВызватьИсключение "Не удалось записать элемент " + Строка(Выборка.Ссылка) + ": " + ОписаниеОшибки();
        КонецПопытки;
    КонецЦикла;
КонецПроцедуры

Вариант 2. Оптимальное решение через запрос с иерархией и транзакцией

Для гарантированного обновления всех вложенных элементов на любой глубине иерархии и обеспечения целостности данных разберем наиболее надежный подход с использованием объекта Запрос и явной транзакции:


&НаСервереБезКонтекста
Процедура ЗаменаЗначенийУЭлементовИерархии(РодительСсылка, НовоеЗначение)
    Если Не ЗначениеЗаполнено(РодительСсылка) Тогда
        Возврат;
    КонецЕсли;

    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Номенклатура.Ссылка КАК Ссылка
        |ИЗ
        |   Справочник.Номенклатура КАК Номенклатура
        |ГДЕ
        |   Номенклатура.Ссылка В ИЕРАРХИИ(&Родитель)
        |   И НЕ Номенклатура.ЭтоГруппа";

    Запрос.УстановитьПараметр("Родитель", РодительСсылка);
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();

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

Рекомендации по организации архитектуры

При реализации подобных механизмов обратим внимание на следующие правила:

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