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