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