Как программно настроить отбор по подразделению отправителя и получателя в динамических списках 1С

Программист 1С v8.3 (Управляемые формы) 1C:Бухгалтерия Управленческий учет IT и автоматизация бизнеса
← На главную

В процессе автоматизации учета крупных компаний перед разработчиками часто возникает задача ограничения видимости документов в списках. Типичный пример: в документе «Перемещение товаров» пользователи конкретного филиала или подразделения должны видеть только «свои» документы. То есть те операции, в которых их подразделение выступает либо в роли отправителя, либо в роли получателя. При этом сотрудники управляющей компании (головного офиса) должны иметь беспрепятственный доступ ко всем документам базы.

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

Постановка задачи и логика работы

Для реализации этой задачи нам потребуется доработать форму списка документа. Нам необходимо при создании формы на сервере определить подразделение текущего пользователя, проверить, относится ли он к руководящему составу (которому можно видеть всё), и, если нет, наложить жесткий отбор. Отбор должен работать по условию ИЛИ:

  1. ПодразделениеОтправитель равен подразделению пользователя;
  2. ИЛИ ПодразделениеПолучатель равен подразделению пользователя.

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

---

Шаг 1. Программное создание группы условий «ИЛИ»

Для объединения нескольких условий отбора по правилу «ИЛИ» в компоновке данных используется специальный объект — группа элементов отбора. Посмотрим на пример кода, который решает эту задачу:


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Получаем подразделение текущего пользователя из параметров сеанса
    ТекущееПодразделение = ПараметрыСеанса.ТекущийПользователь.Подразделение;
    
    // Проверяем, заполнено ли подразделение у пользователя
    Если ЗначениеЗаполнено(ТекущееПодразделение) Тогда
        
        // Проверяем, нужно ли накладывать ограничение (исключаем головное подразделение)
        Если НЕ ПользовательВГлавеУправления(ТекущееПодразделение) Тогда
            
            // 1. Добавляем группу отборов в компоновщик настроек динамического списка
            ГруппаОтбора = Список.КомпоновщикНастроек.Настройки.Отбор.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
            ГруппаОтбора.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИли;
            ГруппаОтбора.Использование = Истина;
            ГруппаОтбора.РежимОтображения = РежимОтображенияЭлементаНастройкиКомпоновкиДанных.Недоступный;
            
            // 2. Добавляем первое условие: Отправитель равен нашему подразделению
            ЭлементОтбора1 = ГруппаОтбора.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
            ЭлементОтбора1.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ПодразделениеОтправитель");
            ЭлементОтбора1.ВидСравнения = ВидСравненияКомпоновкиДанных.ВИерархии; // Позволяет учитывать подчиненные подразделения
            ЭлементОтбора1.ПравоеЗначение = ТекущееПодразделение;
            ЭлементОтбора1.Использование = Истина;
            
            // 3. Добавляем второе условие: Получатель равен нашему подразделению
            ЭлементОтбора2 = ГруппаОтбора.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
            ЭлементОтбора2.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ПодразделениеПолучатель");
            ЭлементОтбора2.ВидСравнения = ВидСравненияКомпоновкиДанных.ВИерархии;
            ЭлементОтбора2.ПравоеЗначение = ТекущееПодразделение;
            ЭлементОтбора2.Использование = Истина;
            
        КонецЕсли;
        
    КонецЕсли;
    
КонецПроцедуры

---

Шаг 2. Реализация проверки «Головного подразделения»

В обсуждении на форуме предлагалось искать головное подразделение по коду "00-000001". Настоятельно рекомендуем избегать «хардкода» строковых кодов в программном коде. Если код элемента в справочнике изменится или при переносе данных создадутся новые дубли, логика программы сломается. Лучше использовать предопределенные элементы, константы или проверять свойства объектов.

Напишем вспомогательную функцию для проверки принадлежности к руководящему подразделению:


&НаСервере
Функция ПользовательВГлавеУправления(ПодразделениеПользователя)
    
    // Безопасный способ определения головного подразделения.
    // Вместо жесткого поиска по коду, используем предопределенный элемент (если он есть)
    // или параметр учета. Здесь для примера покажем поиск по коду, но с проверкой на пустое значение.
    
    ГоловноеПодразделение = Справочники.ПодразделенияОрганизаций.НайтиПоКоду("00-000001");
    
    Если ГоловноеПодразделение.Пустая() Тогда
        Возврат Ложь; // Если не нашли головное по коду, по умолчанию ограничиваем права
    КонецЕсли;
    
    // Проверяем, совпадает ли подразделение с головным или является ли оно его подчиненным (в иерархии)
    Возврат ПодразделениеПользователя.ПринадлежитЭлементу(ГоловноеПодразделение);

КонецФункции

---

Анализ типичных ошибок из обсуждения

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

1. Ошибка удаления элементов отбора в цикле Для каждого

Попытка очистить существующие отборы следующим образом гарантированно приведет к неполному удалению элементов или аварийному завершению работы платформы:


// ТАК ДЕЛАТЬ НЕЛЬЗЯ!
Для каждого ЭлементОтбора Из Список.Отбор.Элементы Цикл 
    Список.Отбор.Элементы.Удалить(ЭлементОтбора); 
КонецЦикла;

Причина ошибки: Во время обхода коллекции методом Для каждого удаление текущего элемента смещает индексы всех последующих элементов назад. В результате цикл пропускает каждый второй элемент. Правильным решением для полной очистки коллекции является использование штатного метода Очистить():

Список.КомпоновщикНастроек.Настройки.Отбор.Элементы.Очистить();

2. Ошибка "Значение не является значением объектного типа (ЛевоеЗначение)"

Эта ошибка возникла в сообщении 23 и 26. Разработчик скопировал кусок кода, в котором пропустил инициализацию переменной ЭлементОтбора в ветке Иначе:


Иначе
    // Переменная ЭлементОтбора не была создана через .Добавить()!
    ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ПодразделениеОрганизации"); 
КонецЕсли;

Решение проблемы: Всегда следите за тем, чтобы перед обращением к свойствам объекта (таким как ЛевоеЗначение, ВидСравнения, ПравоеЗначение) этот объект был физически добавлен в коллекцию и присвоен переменной:


ЭлементОтбора = Список.КомпоновщикНастроек.Настройки.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));

3. Ошибка использования неверного типа группы отбора

В сообщении 10 предлагалось использовать тип ГруппаВыбранныхПолейКомпоновкиДанных. Это грубая ошибка. Данный тип предназначен исключительно для группировки выводимых колонок в отчете или списке, но никак не для фильтрации данных.

Правильный тип: Для создания логических условий И/ИЛИ/НЕ всегда используйте ГруппаЭлементовОтбораКомпоновкиДанных.

---

Оптимизация кода с использованием механизмов БСП

Поскольку разработка ведется в конфигурации «Бухгалтерия КОРП 3.0», в ней по умолчанию присутствует Библиотека стандартных подсистем (БСП). БСП предоставляет готовые программные интерфейсы для работы с отборами динамических списков, что позволяет сократить количество строк кода и повысить его надежность.

Рассмотрим, как установить простой отбор (например, в журнале документов или списке платежных поручений) с помощью функций БСП:


&НаСервере
Процедура УстановитьОтборПоБСП()
    
    ТекущееПодразделение = ПараметрыСеанса.ТекущийПользователь.Подразделение;
    
    // Если пользователь не из управления, ставим отбор в одну строчку
    Если НЕ ПользовательВГлавеУправления(ТекущееПодразделение) Тогда
        
        ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбораДинамическогоСписка(
            Список,                                             // Наш динамический список
            "ПодразделениеОрганизации",                         // Имя поля в списке
            ТекущееПодразделение,                               // Значение отбора
            ВидСравненияКомпоновкиДанных.ВИерархии,             // Вид сравнения
            "Отбор по подразделению",                           // Представление отбора
            Истина,                                             // Использование
            РежимОтображенияЭлементаНастройкиКомпоновкиДанных.Недоступный // Скрываем от пользователя
        );
        
    КонецЕсли;
    
КонецПроцедуры

---

Особенности работы с Журналами документов

В сообщениях 40-55 автор столкнулся с проблемой: отбор отлично работает в документе «Перемещение товаров», но в «Журнале документов (Деньги)» пользователи перестают видеть вообще какие-либо данные.

При работе с журналами документов крайне важно учитывать следующие архитектурные особенности:

  1. Наличие графы в журнале. Поле, по которому вы делаете отбор (например, ПодразделениеОрганизации), обязательно должно быть выведено в качестве Графы журнала в конфигураторе. На вкладке «Графы» проверяемого журнала должно быть настроено соответствие этого поля реквизитам входящих в него документов. Если у какого-то документа нет соответствия этой графе, этот документ полностью исчезнет из видимости при установке отбора.
  2. Документы без реквизита подразделения. Если в журнал попадает документ (например, операция по кассе), у которого реквизит «Подразделение» не заполнен в базе данных, то при жестком отборе Подразделение = [Наше подразделение] этот документ автоматически скрывается. Для таких случаев в отбор необходимо добавлять дополнительное условие «ИЛИ Подразделение НЕ Заполнено».

---

Рекомендация: Безопасность данных на уровне записей (RLS)

Программная установка отборов в форме списка — это отличное решение для обеспечения удобства работы пользователей (чтобы они не путались в чужих документах). Однако это не является надежным средством защиты информации.

Опытный пользователь сможет обойти форму списка и просмотреть чужие перемещения через:

Если перед вами стоит задача жесткого разграничения коммерческих или конфиденциальных данных, лучшим решением будет использование типового механизма RLS (Ограничение доступа на уровне записей), который уже встроен в 1С:Бухгалтерию КОРП. Настройка RLS по подразделениям выполняется в режиме «1С:Предприятие» в разделе Администрирование — Настройки пользователей и прав — Группы доступа без изменения программного кода конфигурации.

← На главную