Как перебрать все документы выбранного типа в 1С 8.3: подробное руководство

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

Разработка интерактивных обработок в среде 1С:Предприятие 8.3 часто требует от нас реализации гибких механизмов выбора данных. Типичный сценарий: пользователю необходимо выбрать определенный вид документа (например, Расходная накладная, Приходная накладная или Перемещение товаров), а затем по кнопке выполнить групповую обработку всех существующих в базе документов этого типа. При этом важно не просто решить задачу, но и сделать это оптимально с точки зрения использования оперативной памяти и быстродействия СУБД. При необходимости можно воспользоваться готовой обработкой для групповой обработки справочников и документов на управляемых формах.

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

---

Постановка задачи и анализ базовых ошибок

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

Если пользователь все же выбирает один документ (например, Расходная накладная №1 от 01.01.2023), мы можем программно извлечь его тип. Однако этот подход неудобен для пользователя. Ниже мы детально проанализируем различные варианты реализации — от простых к наиболее профессиональным.

---

Способ 1. Получение типа из выбранной ссылки (Простой вариант)

Предположим, на форме уже есть реквизит Объект.ПутьДокумент с типом ДокументСсылка. Нам нужно передать этот тип в серверную процедуру и выполнить запрос по всем аналогичным документам.

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


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

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

Важное примечание: Обратите внимание, что мы конструируем текст запроса путем конкатенации строк ("Документ." + ИмяДокумента). В 1С нельзя передать имя таблицы в качестве параметра запроса (через символ &), поэтому динамическое составление текста — это единственный и абсолютно легитимный способ работы с переменными именами таблиц.

---

Способ 2. Правильный выбор типа данных на форме (Через ОписаниеТипов)

Чтобы избавить пользователя от необходимости выбирать конкретную ссылку ради получения типа, спроектируем интерфейс правильно. Создадим на форме реквизит ВыбранныйТип с типом ОписаниеТипов. Настроим его так, чтобы он содержал только типы ссылок на документы.

Рассмотрим по шагам программную настройку этого элемента при создании формы:


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Нам нужно собрать массив всех доступных типов документов конфигурации
    МассивТиповДокументов = Новый Массив;
    
    Для Каждого МетаДокумент Из Метаданные.Документы Цикл
        // Формируем тип "ДокументСсылка.ИмяДокумента" и добавляем в массив
        МассивТиповДокументов.Добавить(Тип("ДокументСсылка." + МетаДокумент.Имя));
    КонецЦикла;
    
    // Ограничиваем поле ввода на форме только выбранными типами
    НовоеОписаниеТипов = Новый ОписаниеТипов(МассивТиповДокументов);
    
    // Предполагается, что на форме создан реквизит "ВыбранныйТипДанных" с типом ОписаниеТипов
    ЭтаФорма.РеквизитЛинкер = НовоеОписаниеТипов; // Либо напрямую ограничиваем элемент формы
    Элементы.ПолеВыбораТипа.ОграничениеТипа = НовоеОписаниеТипов;
    
КонецПроцедуры

При таком подходе при нажатии на кнопку выбора в поле ввода откроется чистое стандартное окно выбора типов документов без вывода списка самих документов. Это эталонный способ проектирования интерфейса для подобных задач.

---

Способ 3. Использование списка выбора (Альтернативный вариант интерфейса)

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


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

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


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

---

Способ 4. Обход документов через менеджер (Без написания запроса)

В платформе 1С существует возможность обхода документов напрямую через менеджер коллекции, используя конструкцию Выбрать(). Выясним, насколько применим этот метод.

Допустим, мы определили имя документа и записали его в переменную ИмяДокумента. Мы можем запустить обход коллекции следующим образом:


&НаСервере
Процедура ОбходЧерезМенеджер(ИмяДокумента)
    
    // Обращаемся к менеджеру конкретного документа динамически
    Выборка = Документы[ИмяДокумента].Выбрать();
    
    Пока Выборка.Следующий() Цикл
        // Проверяем пометку удаления программно
        Если Выборка.ПометкаУдаления Тогда
            Продолжить;
        КонецЕсли;
        
        // Обработка документа
        Сообщить(Выборка.Ссылка);
    КонецЦикла;
    
КонецПроцедуры

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

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

---

Оптимизация работы с транзакциями и памятью

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

  1. Отказ от метода Выгрузить(). При выгрузке результата запроса в таблицу значений вся выборка данных моментально помещается в оперативную память. Используйте последовательный обход Выборка.Следующий() — это позволяет платформе подгружать данные порциями.
  2. Пакетная обработка и транзакции. Запись объектов базы данных (особенно проведение или изменение реквизитов) — очень ресурсоемкая операция. Если изменять каждый объект "на лету" в общем цикле, это приведет к множеству мелких транзакций в СУБД, что сильно замедлит процесс. Объединяйте запись объектов в группы (транзакции) или делайте паузы, чтобы не блокировать таблицы базы данных для других пользователей.
  3. Использование функций БСП. Если вы ведете разработку в типовой конфигурации на базе Библиотеки Стандартных Подсистем (БСП), для получения имени таблицы используйте готовую функцию общего назначения: ОбщегоНазначения.ИмяТаблицыПоСсылке(Ссылка). Это защитит ваш код от ошибок типизации.

Проанализируем пример оптимизированного кода пакетного изменения документов с ограничением транзакционной нагрузки:


&НаСервере
Процедура ВыполнитьОптимизированнуюЗапись(ИмяДокумента)
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Док.Ссылка КАК Ссылка
        |ИЗ
        |	Документ." + ИмяДокумента + " КАК Док
        |ГДЕ
        |	НЕ Док.ПометкаУдаления";
        
    Результат = Запрос.Выполнить();
    Если Результат.Пустой() Тогда
        Возврат;
    КонецЕсли;
    
    Выборка = Результат.Выбрать();
    
    // Счетчик для контроля транзакций
    СчетчикДокументов = 0;
    МаксимумВТранзакции = 100; // Оптимальный размер порции данных
    
    НачатьТранзакцию();
    
    Пока Выборка.Следующий() Цикл
        
        Попытка
            // Получаем объект для записи изменений
            ОбъектДок = Выборка.Ссылка.ПолучитьОбъект();
            
            // Выполняем требуемые действия, например, заполняем какой-то реквизит
            ОбъектДок.Комментарий = "Обработано автоматически " + ТекущаяДата();
            
            // Записываем документ с отключением лишних проверок для ускорения работы
            ОбъектДок.Записать(РежимЗаписиДокумента.Запись);
            
            СчетчикДокументов = СчетчикДокументов + 1;
            
            // Каждые 100 документов фиксируем транзакцию и открываем новую
            Если СчетчикДокументов % МаксимумВТранзакции = 0 Тогда
                ЗафиксироватьТранзакцию();
                НачатьТранзакцию();
            КонецЕсли;
            
        Исключение
            // В случае ошибки отменяем текущую транзакцию, чтобы избежать рассинхронизации данных
            ОтменитьТранзакцию();
            ЗаписатьЛогОшибок(ОписаниеОшибки());
            
            // Возобновляем транзакционный контекст для продолжения цикла
            НачатьТранзакцию();
        КонецПопытки;
        
    КонецЦикла;
    
    // Завершаем последнюю открытую транзакцию
    Если ТранзакцияАктивна() Тогда
        ЗафиксироватьТранзакцию();
    КонецЕсли;
    
КонецПроцедуры

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

← На главную