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