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