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