Значения перечислений во встроенном языке и запросах 1С 8.3: как проверять их без ошибок

· 9 мин

Содержание

Значение перечисления в 1С сравнивают со ссылкой на нужное значение через обычное =: Если ВидОперации = Перечисления.ВидыОпераций.Поступление Тогда. В запросе для того же служит конструкция ЗНАЧЕНИЕ(Перечисление.ВидыОпераций.Поступление), а в клиентском коде управляемой формы — функция ПредопределенноеЗначение("Перечисление.ВидыОпераций.Поступление").

Сравнение со строкой вида ВидОперации = "Поступление" синтаксически допустимо, но всегда вернёт Ложь: слева ссылка, справа строка. Ошибки нет, условие просто никогда не срабатывает. Так проявляется большинство ошибок с перечислениями: код выглядит правильным, а ветка не выполняется.

Что такое значение перечисления с точки зрения платформы

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

Отсюда несколько свойств, которые определяют все способы сравнения:

  • 🔗 Значение — это ссылка. Тип реквизита — ПеречислениеСсылка.ИмяПеречисления. В базе хранится не имя и не синоним, а внутренний идентификатор значения.
  • 🚫 Объекта у перечисления нет. Типа ПеречислениеОбъект не существует: значение нельзя «открыть», изменить или записать. Работают только ссылка и менеджер Перечисления.ИмяПеречисления.
  • 📋 У каждого значения есть имя, синоним и порядковый номер (индекс). Имя — технический идентификатор (Поступление), синоним — то, что видит пользователь («Поступление товаров»), индекс — позиция в списке значений в конфигураторе, начиная с нуля.
  • ⭕ Есть пустое значение. Реквизит типа перечисления, который не заполнили, содержит пустую ссылку. Её получают через Перечисления.ИмяПеречисления.ПустаяСсылка().
ЧтоКак получитьМожно ли на это опираться при сравнении
СсылкаПеречисления.ВидыОпераций.ПоступлениеДа, основной способ
Имя значенияXMLСтрока(Значение)Да, если имя пришло извне (файл, обмен)
ИндексПеречисления.ВидыОпераций.Индекс(Значение)Только для сравнения порядка
СинонимСтрока(Значение)Нет, только для вывода пользователю

Сравнение значения перечисления со ссылкой в коде

Базовый вариант — сравнение двух ссылок. Работают операторы = и <>:

Если Документ.ВидОперации = Перечисления.ВидыОпераций.Поступление Тогда
    ОбработатьПоступление(Документ);
ИначеЕсли Документ.ВидОперации <> Перечисления.ВидыОпераций.Возврат Тогда
    ОбработатьПрочее(Документ);
КонецЕсли;

Если имя значения записано с опечаткой, ошибка возникнет только при выполнении этой строки: Поле объекта не обнаружено (Поступлние). Синтаксический контроль модуля её не поймает, поэтому ветки с редкими значениями стоит хотя бы раз прогнать отладчиком.

Проверка на пустое значение

Есть три равноценных по результату способа:

// 1. Сравнение с пустой ссылкой
Если ВидОперации = Перечисления.ВидыОпераций.ПустаяСсылка() Тогда

// 2. Метод самой ссылки
Если ВидОперации.Пустая() Тогда

// 3. Универсальная функция
Если Не ЗначениеЗаполнено(ВидОперации) Тогда

Третий вариант удобнее, когда тип переменной заранее не известен: ЗначениеЗаполнено() вернёт Ложь и для пустой ссылки, и для Неопределено. Метод Пустая() на значении Неопределено вызовет ошибку — это бывает, если реквизит составного типа или переменную ещё не присвоили.

Проверка, что значение входит в набор

Длинная цепочка Или читается плохо. Вместо неё удобно собрать массив и проверить вхождение:

Допустимые = Новый Массив;
Допустимые.Добавить(Перечисления.ВидыОпераций.Поступление);
Допустимые.Добавить(Перечисления.ВидыОпераций.ПоступлениеИзПереработки);

Если Допустимые.Найти(ВидОперации) <> Неопределено Тогда
    // значение входит в набор
КонецЕсли;

Проверка типа

Если реквизит составного типа и в нём может лежать значение одного из нескольких перечислений, сначала проверьте тип:

Если ТипЗнч(Значение) = Тип("ПеречислениеСсылка.ВидыОпераций") Тогда

Сравнение по имени и индексу значения перечисления

Иногда на входе не ссылка, а строка: имя значения из XML, JSON, настроек или параметра внешней обработки. Тогда есть два пути.

Получить ссылку по имени. Менеджер перечисления поддерживает обращение через квадратные скобки:

ИмяИзФайла = "Поступление";
Значение = Перечисления.ВидыОпераций[ИмяИзФайла];

Если такого имени нет, будет ошибка выполнения. Для данных извне оберните вызов в Попытка … Исключение или проверьте имя по метаданным:

Если Метаданные.Перечисления.ВидыОпераций.ЗначенияПеречисления.Найти(ИмяИзФайла) <> Неопределено Тогда
    Значение = Перечисления.ВидыОпераций[ИмяИзФайла];
КонецЕсли;

Получить имя по ссылке. Для перечислений XMLСтрока() возвращает именно имя значения, а для пустой ссылки — пустую строку:

Если XMLСтрока(ВидОперации) = "Поступление" Тогда

Обратное преобразование — XMLЗначение(Тип("ПеречислениеСсылка.ВидыОпераций"), "Поступление").

Второй способ получить имя — через индекс и метаданные:

Индекс = Перечисления.ВидыОпераций.Индекс(ВидОперации);
Имя = Метаданные.Перечисления.ВидыОпераций.ЗначенияПеречисления[Индекс].Имя;

Для пустой ссылки Индекс() вернёт -1, поэтому перед обращением к коллекции по индексу проверяйте заполненность.

Сравнение по порядку

Операторы < и > для ссылок не работают: платформа выдаст ошибку о том, что сравнение на больше-меньше допустимо только для примитивных типов. Если логика зависит от порядка значений (например, «статус не ниже Согласован»), сравнивайте индексы:

Менеджер = Перечисления.СтатусыЗаявок;
Если Менеджер.Индекс(Статус) >= Менеджер.Индекс(Менеджер.Согласована) Тогда

Индекс зависит от позиции значения в конфигураторе. Если разработчик перетащит значение выше или ниже, логика «по порядку» сломается без единой ошибки. Используйте этот приём, только если порядок значений — часть смысла перечисления и это записано в комментарии к нему.

Сравнение перечислений в запросе

В тексте запроса нельзя обращаться к менеджеру Перечисления. Есть два рабочих варианта.

Конструкция ЗНАЧЕНИЕ() — для фиксированного значения:

ВЫБРАТЬ
    Документ.Ссылка
ИЗ
    Документ.ПоступлениеТоваров КАК Документ
ГДЕ
    Документ.ВидОперации = ЗНАЧЕНИЕ(Перечисление.ВидыОпераций.Поступление)

Обратите внимание: в запросе пишется Перечисление в единственном числе, а в коде — Перечисления. Пустое значение — ЗНАЧЕНИЕ(Перечисление.ВидыОпераций.ПустаяСсылка), без скобок после ПустаяСсылка.

Параметр запроса — когда значение или список вычисляются в коде:

ГДЕ
    Документ.ВидОперации В (&ВидыОпераций)
Запрос.УстановитьПараметр("ВидыОпераций", Допустимые); // массив ссылок

NULL в соединениях

После левого соединения поле перечисления может прийти как NULL. Условие Поле = ЗНАЧЕНИЕ(...) для NULL ложно, Поле <> ЗНАЧЕНИЕ(...) — тоже ложно. Строки «без значения» тихо выпадают из выборки. Если их нужно учитывать, оберните поле:

ГДЕ
    ЕСТЬNULL(Статусы.Статус, ЗНАЧЕНИЕ(Перечисление.СтатусыЗаявок.ПустаяСсылка))
        <> ЗНАЧЕНИЕ(Перечисление.СтатусыЗаявок.Закрыта)

Ветвление внутри запроса

Для вычисляемых колонок используйте ВЫБОР:

ВЫБОР
    КОГДА Документ.ВидОперации = ЗНАЧЕНИЕ(Перечисление.ВидыОпераций.Возврат)
        ТОГДА -Документ.Сумма
    ИНАЧЕ Документ.Сумма
КОНЕЦ КАК СуммаСоЗнаком

Проверка типа в поле составного типа — оператор ССЫЛКА: ГДЕ Регистр.Аналитика ССЫЛКА Перечисление.ВидыОпераций.

Упорядочивание УПОРЯДОЧИТЬ ПО Документ.ВидОперации сортирует значения в том порядке, в котором они стоят в конфигураторе, а не по алфавиту синонимов.

Почему нельзя сравнивать по строковому представлению

Соблазн понятен: Если Строка(ВидОперации) = "Поступление товаров" Тогда читается как обычный текст. Но так делать не стоит:

  • ✏️ Синоним меняется. Разработчик поправит синоним на «Поступление товаров и услуг» — условие перестанет срабатывать, и об этом никто не узнает.
  • 🌐 Синоним зависит от языка. В конфигурации с несколькими языками интерфейса Строка() вернёт текст на языке текущего пользователя.
  • 🐢 Это медленнее. Получение представления — лишняя операция по сравнению со сравнением двух ссылок.
  • 🔍 В запросе ещё хуже. Условие на ПРЕДСТАВЛЕНИЕ(Документ.ВидОперации) не позволяет СУБД использовать индекс по полю, и отбор превращается в перебор.

Строковое представление нужно для вывода: в сообщения, печатные формы, логи. Если в коде требуется строковое сравнение (данные пришли текстом), сравнивайте по имени через XMLСтрока(), а не по синониму.

СпособУстойчив к правке синонимаРаботает в запросеКогда применять
Ссылка =ДаЧерез ЗНАЧЕНИЕ()Всегда по умолчанию
Имя XMLСтрока()ДаНетОбмен, файлы, настройки
ИндексДа, но ломается при перестановкеНетЛогика «по порядку»
Синоним Строка()НетЧерез ПРЕДСТАВЛЕНИЕ(), медленноТолько вывод

Сравнение перечислений на клиенте управляемой формы

В тонком и веб-клиенте менеджер Перечисления недоступен. Код вида

&НаКлиенте
Процедура ВидОперацииПриИзменении(Элемент)
    Если Объект.ВидОперации = Перечисления.ВидыОпераций.Возврат Тогда

упадёт с ошибкой Переменная не определена (Перечисления). На клиенте используйте ПредопределенноеЗначение():

&НаКлиенте
Процедура ВидОперацииПриИзменении(Элемент)
    Если Объект.ВидОперации = ПредопределенноеЗначение("Перечисление.ВидыОпераций.Возврат") Тогда
        Элементы.ОснованиеВозврата.Видимость = Истина;
    КонецЕсли;
КонецПроцедуры

Строка в параметре пишется, как в запросе: Перечисление в единственном числе. Опечатка в имени тоже даст ошибку только при выполнении.

💡 Если одно и то же значение проверяется в нескольких клиентских обработчиках формы, получите его один раз в ПриСозданииНаСервере, положите в реквизит формы или в параметр и используйте дальше. Так и код короче, и серверный вызов не нужен.

Условное оформление и видимость элементов, зависящие от значения перечисления, удобнее настраивать без кода: в условном оформлении формы отбор по реквизиту задаётся выбором значения из списка.

Ошибки при сравнении перечислений и как их найти

Диагностика: выберите симптом

Ещё одна тихая ошибка — сравнение значений разных перечислений с одинаковыми именами. Перечисления.СтатусыЗаказов.Закрыт и Перечисления.СтатусыЗаявок.Закрыт — разные ссылки, и равенство между ними всегда ложно. Встречается при копировании кода из соседнего модуля.

Как писать сравнения перечислений быстрее и чище

Само сравнение двух ссылок дешёвое. Время уходит не на него, а на то, что происходит вокруг.

1. Выносите значение из цикла. Каждое обращение Перечисления.ВидыОпераций.Поступление — это обращение к менеджеру. В цикле по тысячам строк получите ссылку один раз до цикла:

Поступление = Перечисления.ВидыОпераций.Поступление;
Для Каждого Строка Из ТаблицаДанных Цикл
    Если Строка.ВидОперации = Поступление Тогда
        // ...
    КонецЕсли;
КонецЦикла;

2. Фильтруйте в запросе, а не в коде. Если нужны только документы с одним видом операции, отберите их условием ГДЕ, а не выбирайте всё подряд с последующей проверкой в цикле.

3. Заменяйте длинные ИначеЕсли соответствием. Когда каждому значению перечисления отвечает своё действие или своя настройка, ключом соответствия может быть сама ссылка:

СчетаПоВидам = Новый Соответствие;
СчетаПоВидам.Вставить(Перечисления.ВидыОпераций.Поступление, "60.01");
СчетаПоВидам.Вставить(Перечисления.ВидыОпераций.Возврат, "62.01");

КодСчета = СчетаПоВидам.Получить(ВидОперации);
Если КодСчета = Неопределено Тогда
    ВызватьИсключение "Не задан счёт для вида операции " + Строка(ВидОперации);
КонецЕсли;

Новое значение перечисления в такой схеме не проваливается молча в ветку Иначе: код явно сообщит, что для него нет настройки.

4. Не проверяйте одно и то же дважды. Если в начале процедуры уже выяснили, что значение заполнено, повторная проверка ЗначениеЗаполнено() перед каждым сравнением не нужна.

5. Документируйте порядок. Если логика опирается на индексы, напишите об этом в комментарии к перечислению в конфигураторе. Следующий разработчик, который захочет переставить значения «для красоты», увидит предупреждение.

Проверьте себя
Что вернёт условие `ВидОперации = "Поступление"`, если в реквизите лежит значение Поступление?
Как правильно записать пустое значение в тексте запроса?
Чем сравнить значение перечисления в клиентской процедуре формы?

Частые вопросы о сравнении перечислений

Можно ли сравнить значение перечисления с числом?
Нет. Ссылка не приводится к числу, условие будет ложным. Если нужно число, получите индекс методом Перечисления.Имя.Индекс(Значение) и сравнивайте его.
Что вернёт XMLСтрока для пустого значения перечисления?
Пустую строку. Поэтому проверка XMLСтрока(Значение) = "" тоже работает как проверка на пустоту, но ЗначениеЗаполнено() понятнее.
Как сравнить перечисление во внешней обработке, если его нет в конфигурации?
Обращение Перечисления.Имя к несуществующему перечислению вызовет ошибку. Сначала проверьте Метаданные.Перечисления.Найти("Имя") <> Неопределено, а потом обращайтесь к менеджеру через квадратные скобки: Перечисления["Имя"]["Значение"].
Нужно ли перечитывать данные после изменения значений перечисления в конфигураторе?
Ссылки на существующие значения сохраняются при переименовании и перестановке. Удалённое значение превращается в битую ссылку: в данных останется ссылка на объект, которого уже нет, и сравнение с любым действующим значением будет ложным.
Можно ли использовать значение перечисления как ключ структуры?
Нет, ключ структуры — только строка-идентификатор. Используйте соответствие: его ключом может быть ссылка.