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