Сколько копий было сломано относительно RLS на чтение. Пожалуй, это самая главная проблема прав доступа, которую не могли решить программисты 1С.

Все остальное так или иначе делалось кодом, а вот в RLS на чтение приходилось ковыряться в ролях руками. На худой конец — с помощью парсеров, но их никто так и не написал.

Суть проблемы в чем. Допустим, есть RLS по организации и складу.

А затем по некоторым документам нужно добавить RLS по проектам.

При этом нужно было вручную во всех объектах менять RLS-запрос, добавлять отбор по проектам. И шаблоны тут не могли помочь.

Решение я придумал только сейчас, 20 февраля 2026 года. Оно было всегда под носом, как часто бывает.

Оно заключается в том, что для каждого объекта прописываем нужное нам условие в одном шаблоне RLS Общий:

Вот пример такого условия:

#Если "Справочник.Номенклатура" = #ИмяТекущейТаблицы #Тогда
	ГДЕ Истина
#КонецЕсли

#Если "Справочник.Контрагенты" = #ИмяТекущейТаблицы #Тогда
	ГДЕ Ссылка.Наименование ПОДОБНО "а%"
#КонецЕсли


Проверяем на контрагентах, без RLS у нас такой список:

С RLS только те контрагенты, что начинаются на букву «а»:

На практике, конечно, будут более осмысленные тексты запросов, например:

#Если "Справочник.Склады" = #ИмяТекущейТаблицы #Тогда
    ГДЕ Ссылка В (&ДоступныеСклады)
#КонецЕсли

#Если "Документ.ПоступлениеТоваров" = #ИмяТекущейТаблицы #Тогда
    ГДЕ
    Склад В   (&ДоступныеСклады)
    И Организация  В   (&ДоступныеОрганизации)
#КонецЕсли

Сам текст запросов можно по каждому объекту сгенерировать кодом и вставить уже в шаблон.

Наконец-то программисты 1С избавлены от рутины с правами доступа RLS!

Суть моего гениального решения в RLS понятна любому 1с-нику, знакомому с RLS, но попробую объяснить его программистам из других отраслей.

Итак, 1с для каждого объекта (номенклатура, приходная накладная, расходная накладная, …) позволяет наложить ограничение доступа — Record Level Security (RLS).

Но в каждый объект надо заходить и прописывать это ограничение руками. Это может выглядеть так:

  • Приходная накладная: Организация в &ДоступныеОрганизации и Склад в &ДоступныеСклады

  • Расходная накладная: Организация в &ДоступныеОрганизации и Склад в &ДоступныеСклады

  • Организации: Организация в &ДоступныеОрганизации

  • Склады: Склад в &ДоступныеСклады

Как видите, тут есть повторяющиеся фрагменты, поэтому билиотека БСП в 1С вам предложит использовать шаблоны:

  • Приходная накладная: ШаблонПрав(«Организация, Склад»)

  • Расходная накладная: ШаблонПрав(«Организация, Склад»)

  • Организации: ШаблонПрав(«Организация»)

  • Склады: ШаблонПрав(«Склад»)

Стало выглядеть короче, да?

Но что, если вы захотите сделать отбор по проекту? Правила с учетом применения шаблонов должны выглядеть так:

  • Приходная накладная: ШаблонПрав(«Организация, Склад, Проект»)

  • Расходная накладная: ШаблонПрав(«Организация, Склад, Проект»)

  • Организации: ШаблонПрав(«Организация»)

  • Склады: ШаблонПрав(«Склад»)

  • Проекыт: ШаблонПрав(«Проект»)

Вы обратили внимание, что мы должны зайти в каждый объект и вручную поправить правило. А таких шаблонов может быть много, несколько десятков, а то и сотни.

Поэтому желание вносить изменения в RLS на чтение очень быстро исчезает. К тому же все эти доработки слетят при обновлении 1С. Выход был бы в написании парсера ролей, чтобы выгрузить роли в XML-файлы, поправить, потом загрузить их обратно, но это довольно сложно и никто так и не сподобился его написать.

Суть моего решения в том, чтобы всем объектам прописать общее правило Основной:

  • Приходная накладная: Основной

  • Расходная накладная: Основной

  • Организации: Основной

  • Склады: Основной

  • Проекты: Основной

А уже содержание шаблона Основной будет представлять собой один длинный текст, где для каждого вида объектов будет свое правило RLS:

#Если "Справочник.Склады" = #ИмяТекущейТаблицы #Тогда
    ГДЕ Ссылка В (&ДоступныеСклады)
#КонецЕсли

#Если "Справочник.Организации" = #ИмяТекущейТаблицы #Тогда
    ГДЕ Ссылка В (&ДоступныеОрганизации)
#КонецЕсли

#Если "Документ.ПриходнаяНакладная" = #ИмяТекущейТаблицы #Тогда
    ГДЕ
    Склад В   (&ДоступныеСклады)
    И Организация  В   (&ДоступныеОрганизации)
#КонецЕсли

#Если "Документ.РасходнаяНакладная" = #ИмяТекущейТаблицы #Тогда
    ГДЕ
    Склад В   (&ДоступныеСклады)
    И Организация  В   (&ДоступныеОрганизации)
#КонецЕсли

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

#Если "Справочник.Склады" = #ИмяТекущейТаблицы #Тогда
    ГДЕ Ссылка В (&ДоступныеСклады)
#КонецЕсли

#Если "Справочник.Организации" = #ИмяТекущейТаблицы #Тогда
    ГДЕ Ссылка В (&ДоступныеОрганизации)
#КонецЕсли

#Если "Справочник.Проекты" = #ИмяТекущейТаблицы #Тогда
    ГДЕ Ссылка В (&ДоступныеПроекты)
#КонецЕсли

#Если "Документ.ПриходнаяНакладная" = #ИмяТекущейТаблицы #Тогда
    ГДЕ
    Склад В   (&ДоступныеСклады)
    И Организация  В   (&ДоступныеОрганизации)
    И Проект  В   (&ДоступныеПроекты)
#КонецЕсли

#Если "Документ.РасходнаяНакладная" = #ИмяТекущейТаблицы #Тогда
    ГДЕ
    Склад В   (&ДоступныеСклады)
    И Организация  В   (&ДоступныеОрганизации)
    И Проект  В   (&ДоступныеПроекты)
#КонецЕсли

И, конечно, этот шаблон лучше и проще генерировать программно.

Основное преимущество в том, что убирается рутинный ручной труд и RLS на чтение пишутся автоматизированно. А ведь раньше это было самой сложной темой именно из-за того, что нужно было прописывать изменения во все роли.

И все это возможно было сделать уже 10 лет назад, при появлении RLS.

Комментарии (20)


  1. Junecat
    03.03.2026 23:42

    У меня небольшой вопрос к автору: Вы знаете, что фраза «окончательное решение вопроса» несёт в себе некоторый культурный подтекст, и обычно используется только в связи с этим контекстом?

    я пишу это потому, что кое кого за такую формулировку уже заминусовали…


    1. fixin Автор
      03.03.2026 23:42

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

      я хоть и программист, но много общался с женщинами, это хорошее противоядие. Рекомендую.


      1. unkas42
        03.03.2026 23:42

        Эта "пластинка" про буквоедство - очень удобное оправдание неспособности корректно формулировать мысли.
        Значит, корректировка типовых шаблонов у вас слетит при обновлении. А замена типовых шаблонов на ваш Основной не слетит.


        1. fixin Автор
          03.03.2026 23:42

          иногда буквоедство это признак граммар-наци.

          я предлагаю метод, а как вы будете его использовать - совместно с типовыми ролями или вместо типовых ролей, дело хозяйское.

          главное, у программистов по RLS на чтение теперь появился выбор. Раньше его, все 10 лет существования RLS не было. В этом ценность.

          Я предлагаю на чтение делать одну роль.

          На изменения, конечно, контроль в модулях.

          Так будет законченная и логичная система прав в отличии от типовой галочной стыдобы в 1С.


  1. itmind
    03.03.2026 23:42

    Влияет ли на производительность шаблон с большим количеством #Если?


    1. fixin Автор
      03.03.2026 23:42

      нет, все компилируется на этапе сохранения метаданных (компиляции), т.е. это не run-time вычисления.

      Скорее это аналог макросов в Си, где просто подставляются параметры в результирующий код.

      Кстати, так не хватает макросов в Cи Шарп...


      1. Naf2000
        03.03.2026 23:42

        C# содержит директивы препроцессора. Они не настолько развиты как в Си, но с другой стороны, что они там и не нужны


        1. fixin Автор
          03.03.2026 23:42

          я про макросы #Define, это мощная штука.


          1. yesenin_toxa
            03.03.2026 23:42

            В C# есть кодогенерация https://habr.com/en/articles/906778/ и сорсгенерация. Если не достаточно дженериков и экстеншенов, то не слабее штука.


            1. fixin Автор
              03.03.2026 23:42

              ну вот пример.

              В зависимости от типа устройства может быть возвращена структура A1, A2 или A3.

              Мне надо с результатом вызвать процедуру Work и Bench.

              Например:

              R = GetValueFromDevice();

              If R.code = 1 {

              V = R.value as A1;

              Work_A1(v);

              Bench_A1(v);

              }

              elseIf R.code = 2 {

              V = R.value as A2;

              Work_A2(v);

              Bench_A2(v);

              }

              elseIf R.code = 3 {

              V = R.value as A3;

              Work_A3(v);

              Bench_A3(v);

              }

              Конечно, можно городить обертки над структурами, но это не так красиво, как макросы.


              1. Naf2000
                03.03.2026 23:42

                switch (code)
                {
                    case 1: Test<A1>(value, Work_A1,Bench_A1); break;
                    case 2: Test<A1>(value, Work_A2, Bench_A2); break;
                    case 3: Test<A3>(value, Work_A3, Bench_A3); break;
                }
                
                static void Test<T>(object a, Action<T> work, Action<T> bench)
                {
                    T t = (T)a;
                    work(t);
                    bench(t);
                }
                


                1. fixin Автор
                  03.03.2026 23:42

                  Выглядит неплохо. Применю на практике по оказии, спасибо.


              1. yesenin_toxa
                03.03.2026 23:42

                Если тип устройства, то, конечно, надо смотреть, что за структура -- может надо довести до CLR. Но пусть все просто будет.

                1. Путь интерфейсов (он же про дженерики)

                  // можно и без него, но просто понимать что что-то есть внутри
                  abstract class BaseA { 
                    public BaseA(string value) {
                      Value = value; // какое-то создание
                    }
                    public string Value {get; init;}
                  }
                  
                  // можно методы объеденить
                  interface IA {
                    void Work();
                    void Bench();
                  }
                  
                  // эти классы по ходу у тебя уже есть
                  // им только добавить интерфейсы
                  class A1: BaseA, IA {
                    public A1(string value): base(value) {}
                    public void Work() {
                      // логика для Work_A1(this.Value)
                    }
                    public void Bench() {
                    	// логика для и Bench_A1(this.Value)
                    }
                  }
                  class A2: BaseA, IA {
                    public A2(string value): base(value) {}
                    public void Work() {
                      // логика для Work_A1(this.Value)
                    }
                    public void Bench() {
                    	// логика для и Bench_A2(this.Value)
                    }
                  }
                  class A3: BaseA, IA {
                    public A3(string value): base(value) {}
                    public void Work() {
                      // логика для Work_A3(this.Value)
                    }
                    public void Bench() {
                    	// логика для и Bench_A3(this.Value)
                    }
                  }
                  
                  // конечный код
                  var R = GetValueFromDevice();
                  var concrete_A = R.value () switch {
                      1 => new A1(R.value),
                      2 => new A2(R.value),
                      3 => new A3(R.value),
                  	_ => throw new Exception("неизвестное устройство")
                  };
                  concrete_A.Work();
                  concrete_A.Bench();
                  
                  // и даже можно будет так
                  var deviceList = new List<IA>
                    {
                      new A1("a1"), new A2("a2"), new A2("a2-2"))
                    };
                  foreach (var device in deviceList)
                  {
                    device.Work();
                    device.Bench();
                  }
                2. Путь экстеншенов

                  abstract class BaseA { 
                    public BaseA(string value) {
                      Value = value; // какое-то создание
                    }
                    public string Value {get; init;}
                  }
                  
                  class A1: BaseA {
                    public A1(string value): base(value) {}
                  }
                  class A2: BaseA {
                    public A2(string value): base(value) {}
                  }
                  class A3: BaseA {
                    public A3(string value): base(value) {}
                  }
                  
                  // если не интерфейсы, то вот
                  // и для краткости объединяю методы целевые
                  static class AExtensions {
                  	static void WorkAndBench(this A1 a) {
                  		// Work_A1(a.Value);
                  		// Bench_A1(a.Value);
                  	}
                  
                  	static void WorkAndBench(this A2 a) {
                  		// Work_A2(a.Value);
                  		// Bench_A2(a.Value);
                  	}
                  
                  	static void WorkAndBench(this A3 a) {
                  		// Work_A3(a.Value);
                  		// Bench_A3(a.Value);
                  	}
                  }
                  
                  // конечный код
                  var R = GetValueFromDevice();
                  var concrete_A = R.value () switch {
                      1 => new A1(R.value),
                      2 => new A2(R.value),
                      3 => new A3(R.value),
                  	_ => throw new Exception("неизвестное устройство")
                  };
                  concrete_A.WorkAndBench();
                  
                  // с колекцией тоже прокатит
                3. А так -- патерн chain of responsibility или какой-то другой пайплайн, если девайс один.

                Но если вариантов десятки, то генерацию подключать.


              1. yesenin_toxa
                03.03.2026 23:42

                А вообще кажется классы не нужны и можно так

                var dict = new Dictionary<int, Action<string>> {
                 {1, value => {Work_A1(value); Bench_A1(value);)},
                 {2, value => {Work_A2(value); Bench_A2(value);)},
                 {3, value => {Work_A3(value); Bench_A3(value);)}
                };
                
                R = GetValueFromDevice();
                dict[R](R.value);


  1. Naf2000
    03.03.2026 23:42

    1. В производительном режиме типовых 1С (библиотека БСП) не требуется менять шаблоны RLS. Делается это иначе, например тут описано https://infostart.ru/1c/articles/1656341/

    2. Вам придется этот шаблон скопировать во все роли, использующие RLS. Главное ничего не пропустить. Кроме того, т.к. шаблон хранить ВСЕ правила - это путь к коллизиям файлов при групповой разработке. Во всех ролях.

    3. Ваши примеры не покрывают размещения пользователя в разных областях ограничений. Например, в типовых решениях пользователя можно поместить в группу доступа с ограничением склад "Север", а также в группу доступа с ограничением клиенты менеджера "Вася". В итоге он будет видеть документы по складу "Север" ИЛИ по менеджеру "Вася".


    1. fixin Автор
      03.03.2026 23:42

      1. при чем тут производительный режим, у которого своя "тележка недостатков"

      2. нет. мне достаточно иметь только одну роль с RLS, если одна роль расово неприемлема и хочется прям разделения по ролям, то во всех ролях, где доступен просмотр, пишите Где ЛОЖЬ, чтобы они не давали право на просмотр.

      3. В статье этого нет, но я приводил вам пример тут https://infostart.ru/1c/articles/2622960/ (в сообщении номер 4), как это решается в "моём случае". Ничего не мешает использовать справочник групп доступа и в моем решении. Методу это не противоречит.


      1. Naf2000
        03.03.2026 23:42

        1. Какие недостатки типового производительного режима по сравнению с данным решением?

        2. Роль ГДЕ ЛОЖЬ и Роль где отключено право чтения это совершенно разное поведение системы.

        3. Вы меня с кем-то путаете.


        1. fixin Автор
          03.03.2026 23:42

          1. Производительный режим - https://infostart.ru/1c/articles/1656341/, недостатки:
            - нужно рассчитывать по-объектно и хранить эти доступы.
            - тяжеловесность решения
            - сложность добавления своих отборов, впрочем как и все, что взаимодействует с монструозной БСП.
            Помню в Документообороте 2.0 права доступа по-объектно пересчитывались сутками на больших объемах.
            Да, это очевидная попытка снизить сложность настройки RLS на чтение, которая была, пока я не предложил этот метод. Этот метод побивает производительный режим.

          2. в чем разница? роль с RLS одна, она как раз управляет доступностью объектов на просмотр. Мне достаточно одной роли, это полностью закрывает проблему с видимостью через RLS. Это удобно.

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


          1. Naf2000
            03.03.2026 23:42

            1. В производительном режиме делается иначе, то есть заведомо написали ложь. По поводу "тяжеловесности" и "монструозности" - это все слова, надо подкреплять фактами. Покажите свой продукт, сделайте сравнения

            2. Вы действительно не знаете, чем отсутствие прав отличается от прав, где недоступны все записи? А если на этом принимается системой решение. Если забыть дать право на таблицу - в первом случае у вас выкинет исключение - это плохо конечно, во втором случае ошибок не будет, но будет получен неверный результат, который возможно обнаружат не сразу.

            3. Исходя из написанного - "переобуваемся" на ходу, довешиваем новые данные и... вот уже тоже самое, только свой велосипед, родной )))


            1. fixin Автор
              03.03.2026 23:42

              -- В производительном режиме делается иначе, то есть заведомо написали ложь.

              что не так, напишите своими словами.

              -- По поводу "тяжеловесности" и "монструозности" - это все слова, надо подкреплять фактами. Покажите свой продукт, сделайте сравнения

              несомненно, но перспективы метода уже неплохи. Я давно искал такой. Жаль, раньше не знал, думаю и сейчас кто-то в поисках, им пригодится.

              -- Вы действительно не знаете, чем отсутствие прав отличается от прав, где недоступны все записи? А если на этом принимается системой решение. Если забыть дать право на таблицу - в первом случае у вас выкинет исключение - это плохо конечно, во втором случае ошибок не будет, но будет получен неверный результат, который возможно обнаружат не сразу.

              приведите пример, не понимаю о чем вы.

              -- Исходя из написанного - "переобуваемся" на ходу, довешиваем новые данные и... вот уже тоже самое, только свой велосипед, родной )))

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