Как правильно реализовать проверку запрета изменений: на уровне регистра или документа?

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

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

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

Идея: Проверка в модуле набора записей регистра

Давайте разберем ситуацию, описанную автором вопроса. Есть некий периодический или непериодический регистр сведений, содержащий ресурс или измерение, отвечающее за блокировку (назовем его условно ЗапретИзменения). Автор попытался реализовать логику следующим образом:

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

Автор сообщает: "В модуле набора записей... В процедуре перед записью добавил функцию, которая будет проверять 'Запрет' и при проведении документа выдает ошибку, что стоит запрет".

Почему этот подход не работает для всех документов?

Давайте проанализируем критическое замечание, которое прозвучало в обсуждении и раскрывает суть работы платформы 1С.

Ключевой вопрос: С чего мы решили, что проверяемый документ вообще что-то пишет в этот регистр сведений?

Механизм событий в 1С работает строго по цепочке вызовов. Давайте посмотрим на последовательность событий при записи документа:

  1. Срабатывает событие ПередЗаписью в модуле объекта документа.
  2. Срабатывает событие ОбработкаПроведения (если документ проводится).
  3. Внутри обработки проведения документ формирует движения по регистрам (Накопления, Бухгалтерии и т.д.).
  4. Срабатывает событие ПриЗаписи в модуле объекта документа.

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

Если документ (например, "РеализацияТоваровУслуг") не делает движений по нашему регистру сведений "ЗапретИзменения" (а он, скорее всего, не делает, так как это регистр настроек), то модуль набора записей просто никогда не будет вызван.

Таким образом, попытка спрятать логику проверки "в регистр", чтобы не трогать документы, обречена на провал. Документ запишется, платформа даже не "заглянет" в модуль регистра, так как нет команды записи в этот регистр.

Как реализовать проверку правильно?

Чтобы решить задачу "сделать проверку не в каждом документе вручную", в платформе 1С предусмотрен специальный механизм — Подписки на события.

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

1. Создание Подписки на событие

В дереве конфигурации в ветке "Общие" -> "Подписки на события" создадим новую подписку.

2. Реализация логики в Общем модуле

В общем модуле (он должен иметь галочки "Сервер" и "Вызов сервера") создадим процедуру. Рассмотрим пример кода, который реализует проверку.


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

КонецПроцедуры

Функция ЕстьЗапретНаИзменение() должна обращаться к вашему регистру сведений. Поскольку эта проверка будет выполняться при каждой записи документа, крайне важно оптимизировать получение данных.

3. Оптимизация получения данных (Кэширование)

Не стоит делать запрос к базе данных при каждой записи документа, если настройка запрета меняется редко. Давайте используем модуль с повторным использованием возвращаемых значений (кэш на время сеанса или вызова).


// В общем модуле с галочкой "Повторное использование возвращаемых значений" -> "На время сеанса"
Функция ПолучитьЗначениеЗапрета() Экспорт
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Настройки.ЗапретИзменения КАК Запрет
        |ИЗ
        |   РегистрСведений.НастройкиСистемы КАК Настройки";
        
    Результат = Запрос.Выполнить();
    Если Результат.Пустой() Тогда
        Возврат Ложь;
    КонецЕсли;
    
    Выборка = Результат.Выбрать();
    Выборка.Следующий();
    
    Возврат Выборка.Запрет;
    
КонецФункции

Теперь в нашей процедуре подписки мы просто вызываем эту кэшируемую функцию:


// ... внутри обработчика подписки
Если ОбщегоНазначенияПовтИсп.ПолучитьЗначениеЗапрета() Тогда
   // ... блокируем запись
КонецЕсли;

Резюме

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