В процессе разработки и поддержки конфигураций на платформе 1С:Предприятие разработчики часто сталкиваются со странным поведением системы: один и тот же запрос при одинаковых входящих параметрах выдает разные результаты для разных пользователей. Особенно загадочно это выглядит, когда у пользователя с полными правами запрос возвращает заполненную таблицу данных, а у менеджера с ограниченными правами — пустой результат. При этом запуск этого же запроса в консоли под обоими пользователями выдает абсолютно одинаковый (успешный) результат.
В этой статье мы подробно разберем, почему возникает такая проблема на примере конфигурации 1С:Комплексная автоматизация 1.1 (КА 1.1), как работает механизм динамической модификации запросов и как правильно диагностировать и устранять подобные расхождения.
Рассмотрим ситуацию на примере реального запроса из общего модуля УправлениеЗапасами (функция ПолучитьТаблицуОстатковПоСериям). Ниже представлен текст запроса, который собирает остатки по складам и организациям:
ВЫБРАТЬ РАЗРЕШЕННЫЕ
ВложенныйЗапрос.флОстатокОрганизации КАК флОстатокОрганизации,
ВложенныйЗапрос.Номенклатура КАК Номенклатура,
ВложенныйЗапрос.Номенклатура.ЕдиницаХраненияОстатков.Коэффициент КАК КоэффициентХраненияОстатков,
ВложенныйЗапрос.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
ВложенныйЗапрос.СерияНоменклатуры КАК СерияНоменклатуры,
ВложенныйЗапрос.СерияНоменклатуры.СрокГодности КАК СрокГодности,
ПРЕДСТАВЛЕНИЕ(ВложенныйЗапрос.СерияНоменклатуры) КАК СерияНоменклатурыПредставление,
ВложенныйЗапрос.Качество КАК Качество,
ВложенныйЗапрос.Склад КАК Склад,
МИНИМУМ(ВложенныйЗапрос.Остаток) КАК Остаток,
МИНИМУМ(ВложенныйЗапрос.ОстатокКПередаче) КАК ОстатокКПередаче
ИЗ
(ВЫБРАТЬ
ложь КАК флОстатокОрганизации,
ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
ТоварыНаСкладахОстатки.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
ТоварыНаСкладахОстатки.СерияНоменклатуры КАК СерияНоменклатуры,
ТоварыНаСкладахОстатки.СерияНоменклатуры.СрокГодности КАК СрокГодности,
ПРЕДСТАВЛЕНИЕ(ТоварыНаСкладахОстатки.СерияНоменклатуры) КАК СерияНоменклатурыПредставление,
ТоварыНаСкладахОстатки.Качество КАК Качество,
ТоварыНаСкладахОстатки.Склад КАК Склад,
ТоварыНаСкладахОстатки.КоличествоОстаток КАК Остаток,
ТоварыКПередачеСоСкладовОстатки.КоличествоОстаток КАК ОстатокКПередаче
ИЗ
(ВЫБРАТЬ
ТоварыНаСкладахОстатки.Номенклатура,
ТоварыНаСкладахОстатки.ХарактеристикаНоменклатуры,
ТоварыНаСкладахОстатки.СерияНоменклатуры,
ТоварыНаСкладахОстатки.Качество,
ТоварыНаСкладахОстатки.Склад,
СУММА(ТоварыНаСкладахОстатки.КоличествоОстаток) КАК КоличествоОстаток
ИЗ
(ВЫБРАТЬ
ТоварыНаСкладах.Номенклатура,
ТоварыНаСкладах.ХарактеристикаНоменклатуры,
ТоварыНаСкладах.СерияНоменклатуры,
ТоварыНаСкладах.Качество,
ТоварыНаСкладах.Склад,
ТоварыНаСкладах.КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&КонДата, Склад В (&Склад) И Номенклатура В (&Номенклатура)) КАК ТоварыНаСкладах
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ТоварыВРознице.Номенклатура,
ТоварыВРознице.ХарактеристикаНоменклатуры,
ТоварыВРознице.СерияНоменклатуры,
ТоварыВРознице.Качество,
ТоварыВРознице.Склад,
ТоварыВРознице.КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыВРознице.Остатки(&КонДата, Склад В (&Склад) И Номенклатура В (&Номенклатура)) КАК ТоварыВРознице
) КАК ТоварыНаСкладахОстатки
СГРУППИРОВАТЬ ПО
ТоварыНаСкладахОстатки.Номенклатура,
ТоварыНаСкладахОстатки.ХарактеристикаНоменклатуры,
ТоварыНаСкладахОстатки.СерияНоменклатуры,
ТоварыНаСкладахОстатки.Качество,
ТоварыНаСкладахОстатки.Склад
) КАК ТоварыНаСкладахОстатки
ЛЕВОЕ СОЕДИНЕНИЕ
РегистрНакопления.ТоварыКПередачеСоСкладов.Остатки(&КонДата, Склад В (&Склад) И Номенклатура В (&Номенклатура)) КАК ТоварыКПередачеСоСкладовОстатки
ПО
ТоварыНаСкладахОстатки.Номенклатура = ТоварыКПередачеСоСкладовОстатки.Номенклатура
И ТоварыНаСкладахОстатки.ХарактеристикаНоменклатуры = ТоварыКПередачеСоСкладовОстатки.ХарактеристикаНоменклатуры
И ТоварыНаСкладахОстатки.СерияНоменклатуры = ТоварыКПередачеСоСкладовОстатки.СерияНоменклатуры
И ТоварыНаСкладахОстатки.Качество = ТоварыКПередачеСоСкладовОстатки.Качество
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
истина КАК флОстатокОрганизации,
ТоварыОрганизацийОстатки.Номенклатура КАК Номенклатура,
ТоварыОрганизацийОстатки.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
ТоварыОрганизацийОстатки.СерияНоменклатуры КАК СерияНоменклатуры,
ТоварыОрганизацийОстатки.СерияНоменклатуры.СрокГодности КАК СрокГодности,
ПРЕДСТАВЛЕНИЕ(ТоварыОрганизацийОстатки.СерияНоменклатуры) КАК СерияНоменклатурыПредставление,
ТоварыОрганизацийОстатки.Качество КАК Качество,
ТоварыОрганизацийОстатки.Склад КАК Склад,
ТоварыОрганизацийОстатки.КоличествоОстаток КАК Остаток,
ТоварыКПередачеОрганизацийОрганизацийОстатки.КоличествоОстаток КАК ОстатокКПередаче
ИЗ
РегистрНакопления.ТоварыОрганизаций.Остатки(&КонДата, Номенклатура В (&Номенклатура) И Комиссионер = &Комиссионер И Склад В (&Склад)) КАК ТоварыОрганизацийОстатки
ЛЕВОЕ СОЕДИНЕНИЕ
РегистрНакопления.ТоварыКПередачеОрганизаций.Остатки(&КонДата, Номенклатура В (&Номенклатура) И Склад В (&Склад)) КАК ТоварыКПередачеОрганизацийОрганизацийОстатки
ПО
ТоварыОрганизацийОстатки.Номенклатура = ТоварыКПередачеОрганизацийОрганизацийОстатки.Номенклатура
И ТоварыОрганизацийОстатки.ХарактеристикаНоменклатуры = ТоварыКПередачеОрганизацийОрганизацийОстатки.ХарактеристикаНоменклатуры
И ТоварыОрганизацийОстатки.СерияНоменклатуры = ТоварыКПередачеОрганизацийОрганизацийОстатки.СерияНоменклатуры
И ТоварыОрганизацийОстатки.Организация = ТоварыКПередачеОрганизацийОрганизацийОстатки.Организация
И ТоварыОрганизацийОстатки.Качество = ТоварыКПередачеОрганизацийОрганизацийОстатки.Качество
И ТоварыОрганизацийОстатки.Склад = ТоварыКПередачеОрганизацийОрганизацийОстатки.Склад) КАК ВложенныйЗапрос
СГРУППИРОВАТЬ ПО
ВложенныйЗапрос.флОстатокОрганизации,
ВложенныйЗапрос.Номенклатура,
ВложенныйЗапрос.ХарактеристикаНоменклатуры,
ВложенныйЗапрос.СерияНоменклатуры,
ВложенныйЗапрос.Качество,
ВложенныйЗапрос.Склад
При выполнении этого кода менеджер получает пустой результат по второй части запроса (после ОБЪЕДИНИТЬ ВСЕ), где выбираются остатки из регистра ТоварыОрганизаций. Директор же видит эти остатки в полном объеме.
Первое подозрение падает на ограничение доступа на уровне записей (RLS — Row Level Security) или на кэш данных. Однако детальный анализ кода показывает, что проблема кроется в динамической модификации текста запроса конфигурацией перед его отправкой на выполнение СУБД.
Проанализируем программную цепочку, которая формирует итоговый текст запроса. В типовых конфигурациях (таких как КА 1.1, УПП 1.3) текст запроса часто не является статичным. В зависимости от настроек прав пользователей и учетной политики программа модифицирует строковое представление запроса с помощью функций типа СтрЗаменить().
На итоговый текст запроса в данном случае влияют два важных фактора:
УправлениеДопПравамиПользователей.РазрешеноПревышениеОстаткаТоваровОрганизации(Организация) проверяет, установлено ли у текущего пользователя право на превышение остатков организации.ВестиУчетТоваровОрганизацийВРазрезеСкладов), получаемая через ОбщегоНазначения.ПолучитьПараметрыУчетнойПолитикиУпр().Если у пользователя (например, Директора) разрешено превышение остатков, то система позволяет ему видеть остатки товаров по всем организациям холдинга на данном складе. В этом случае жесткий фильтр по конкретной организации в запрос не добавляется.
Если же у пользователя (Менеджера) это право отключено, программный код принудительно внедряет в параметры виртуальной таблицы остатков регистра ТоварыОрганизаций условие вида:
И Организация = &Организация
В результате менеджер видит остатки только той организации, которая указана в качестве параметра. Если на общем складе лежат товары другой организации холдинга, менеджер гарантированно получит пустой результат по этой части запроса.
Это классическая ловушка для разработчика при отладке. Когда мы перехватываем запрос в отладчике, мы обычно копируем свойство Запрос.Текст.
Если скопировать этот текст до того, как отработали условия замены строк в коде общего модуля, мы получим базовый шаблон запроса без жесткого фильтра по организации. Запуская этот шаблон в консоли запросов под учетной записью менеджера, мы принудительно заставляем СУБД выполнить запрос без ограничения по организации, из-за чего менеджер видит те же данные, что и директор. Консоль выполняет статический текст, в то время как конфигурация выполняет динамически модифицированный код.
В зависимости от требований бизнес-логики компании, мы можем исправить эту проблему несколькими способами.
Если менеджеру действительно необходимо видеть физические остатки товаров других организаций на общем складе для оперативной работы, следует предоставить ему соответствующее право в пользовательском режиме:
Истина, и динамический фильтр по организации перестанет урезать запрос для этого менеджера.Если вы разрабатываете собственную внешнюю печатную форму или отчет, обращение к регистру ТоварыОрганизаций для получения складских остатков может быть методически неверным.
Регистр ТоварыОрганизаций предназначен для учета юридической принадлежности товаров конкретным организациям. Если вашей целью является вывод информации о том, сколько товара физически лежит на складе, используйте регистр накопления ТоварыНаСкладах. Данный регистр не имеет разреза организаций, поэтому менеджеры будут видеть фактические остатки без необходимости настройки дополнительных прав на превышение остатков организаций.
Проанализируем простой пример получения складских остатков по регистру ТоварыНаСкладах:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
| ТоварыНаСкладахОстатки.Склад КАК Склад,
| ТоварыНаСкладахОстатки.КоличествоОстаток КАК КоличествоОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Склад = &Склад И Номенклатура В (&СписокНоменклатуры)) КАК ТоварыНаСкладахОстатки";
Запрос.УстановитьПараметр("Период", РабочаяДата);
Запрос.УстановитьПараметр("Склад", ВыбранныйСклад);
Запрос.УстановитьПараметр("СписокНоменклатуры", МассивТоваров);
РезультатЗапроса = Запрос.Выполнить();
Этот запрос отработает одинаково для всех пользователей (при условии отсутствия жестких ограничений RLS на чтение самого регистра ТоварыНаСкладах) и вернет реальное количество товара на складе.
При возникновении разницы в результатах запросов у пользователей с разными правами всегда проверяйте:
СтрЗаменить, ШаблонЗапроса, а также вызовы функций проверки дополнительных прав пользователей).