Разработка интерактивных обработок в среде 1С:Предприятие 8.3 часто требует от нас реализации гибких механизмов выбора данных. Типичный сценарий: пользователю необходимо выбрать определенный вид документа (например, Расходная накладная, Приходная накладная или Перемещение товаров), а затем по кнопке выполнить групповую обработку всех существующих в базе документов этого типа. При этом важно не просто решить задачу, но и сделать это оптимально с точки зрения использования оперативной памяти и быстродействия СУБД. При необходимости можно воспользоваться готовой обработкой для групповой обработки справочников и документов на управляемых формах.
В этой статье мы подробно разберем, как правильно передавать выбранные типы данных между клиентом и сервером, как грамотно организовать интерфейс выбора на управляемой форме и как эффективно обойти документы в цикле, избегая типичных ошибок новичков.
---
Рассмотрим ситуацию, когда начинающий разработчик размещает на форме реквизит с типом ДокументСсылка (составной тип данных, включающий все документы конфигурации). При этом возникает интерфейсная проблема: платформа заставляет пользователя выбирать конкретный экземпляр документа из списка, хотя логика обработки требует выбрать лишь сам тип (метаданные).
Если пользователь все же выбирает один документ (например, Расходная накладная №1 от 01.01.2023), мы можем программно извлечь его тип. Однако этот подход неудобен для пользователя. Ниже мы детально проанализируем различные варианты реализации — от простых к наиболее профессиональным.
---
Предположим, на форме уже есть реквизит Объект.ПутьДокумент с типом ДокументСсылка. Нам нужно передать этот тип в серверную процедуру и выполнить запрос по всем аналогичным документам.
Проанализируем, как правильно передать контекст с клиента на сервер и динамически построить запрос. На клиенте мы проверяем заполненность реквизита, а на сервере получаем имя таблицы метаданных и выполняем запрос.
&НаКлиенте
Процедура ВыполнитьОбработку(Команда)
// Проверяем, выбрал ли пользователь хоть какой-то документ
Если Не ЗначениеЗаполнено(Объект.ПутьДокумент) Тогда
ПоказатьПредупреждение(, "Пожалуйста, выберите документ-шаблон для определения типа!");
Возврат;
КонецЕсли;
// Вызываем серверный контекст для выполнения тяжелых операций с БД
ОбработатьДокументыНаСервере(Объект.ПутьДокумент);
КонецПроцедуры
&НаСервере
Процедура ОбработатьДокументыНаСервере(Знач ШаблонДокумента)
// Получаем имя метаданных документа (например, "РеализацияТоваровУслуг")
ИмяДокумента = ШаблонДокумента.Метаданные().Имя;
// Строим динамический запрос
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Док.Ссылка КАК Ссылка
|ИЗ
| Документ." + ИмяДокумента + " КАК Док
|ГДЕ
| НЕ Док.ПометкаУдаления";
РезультатЗапроса = Запрос.Выполнить();
// Используем выборку для экономии оперативной памяти
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Здесь выполняется логика обработки конкретного документа
// Например, чтение реквизитов или анализ данных
Прогресс = Выборка.Ссылка;
КонецЦикла;
КонецПроцедуры
Важное примечание: Обратите внимание, что мы конструируем текст запроса путем конкатенации строк ("Документ." + ИмяДокумента). В 1С нельзя передать имя таблицы в качестве параметра запроса (через символ &), поэтому динамическое составление текста — это единственный и абсолютно легитимный способ работы с переменными именами таблиц.
---
Чтобы избавить пользователя от необходимости выбирать конкретную ссылку ради получения типа, спроектируем интерфейс правильно. Создадим на форме реквизит ВыбранныйТип с типом ОписаниеТипов. Настроим его так, чтобы он содержал только типы ссылок на документы.
Рассмотрим по шагам программную настройку этого элемента при создании формы:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Нам нужно собрать массив всех доступных типов документов конфигурации
МассивТиповДокументов = Новый Массив;
Для Каждого МетаДокумент Из Метаданные.Документы Цикл
// Формируем тип "ДокументСсылка.ИмяДокумента" и добавляем в массив
МассивТиповДокументов.Добавить(Тип("ДокументСсылка." + МетаДокумент.Имя));
КонецЦикла;
// Ограничиваем поле ввода на форме только выбранными типами
НовоеОписаниеТипов = Новый ОписаниеТипов(МассивТиповДокументов);
// Предполагается, что на форме создан реквизит "ВыбранныйТипДанных" с типом ОписаниеТипов
ЭтаФорма.РеквизитЛинкер = НовоеОписаниеТипов; // Либо напрямую ограничиваем элемент формы
Элементы.ПолеВыбораТипа.ОграничениеТипа = НовоеОписаниеТипов;
КонецПроцедуры
При таком подходе при нажатии на кнопку выбора в поле ввода откроется чистое стандартное окно выбора типов документов без вывода списка самих документов. Это эталонный способ проектирования интерфейса для подобных задач.
---
Если документов в конфигурации немного или нам нужно отфильтровать этот список по какому-то признаку, мы можем вручную заполнить выпадающий список выбора текстовыми представлениями и реальными типами метаданных. Рассмотрим, как это реализовать:
&НаСервере
Процедура ЗаполнитьСписокВыбораТипов()
Элементы.ИмяРеквизитаФормыСписок.СписокВыбора.Очистить();
Для Каждого МетаДок Из Метаданные.Документы Цикл
// Исключаем системные или вспомогательные документы, если необходимо
Если МетаДок.Имя = "Удалить_СтарыйДокумент" Тогда
Продолжить;
КонецЕсли;
// Получаем тип документа
ТекущийТип = Тип("ДокументСсылка." + МетаДок.Имя);
// Добавляем элемент в список выбора: Значение - Тип, Представление - Синоним документа
Элементы.ИмяРеквизитаФормыСписок.СписокВыбора.Добавить(ТекущийТип, МетаДок.Синоним);
КонецЦикла;
// Сортируем список для удобства пользователя
Элементы.ИмяРеквизитаФормыСписок.СписокВыбора.СортироватьПоПредставлению();
КонецПроцедуры
После того как пользователь выберет элемент из такого списка, в реквизите формы сохранится чистый тип Тип. Чтобы получить имя документа для построения запроса на сервере, выполним следующий код:
&НаСервере
Функция ПолучитьИмяДокументаПоТипу(ВыбранныйТип)
МетаданныеОбъекта = Метаданные.НайтиПоТипу(ВыбранныйТип);
Если МетаданныеОбъекта <> Неопределено Тогда
Возврат МетаданныеОбъекта.Имя;
КонецЕсли;
Возврат "";
КонецФункции
---
В платформе 1С существует возможность обхода документов напрямую через менеджер коллекции, используя конструкцию Выбрать(). Выясним, насколько применим этот метод.
Допустим, мы определили имя документа и записали его в переменную ИмяДокумента. Мы можем запустить обход коллекции следующим образом:
&НаСервере
Процедура ОбходЧерезМенеджер(ИмяДокумента)
// Обращаемся к менеджеру конкретного документа динамически
Выборка = Документы[ИмяДокумента].Выбрать();
Пока Выборка.Следующий() Цикл
// Проверяем пометку удаления программно
Если Выборка.ПометкаУдаления Тогда
Продолжить;
КонецЕсли;
// Обработка документа
Сообщить(Выборка.Ссылка);
КонецЦикла;
КонецПроцедуры
Важный анализ производительности: Использование менеджера и метода Выбрать() крайне не рекомендуется на реальных базах данных больших объемов. Данный метод считывает абсолютно все реквизиты и табличные части документов из базы данных в оперативную память сервера приложений. Если документов тысячи, это приведет к серьезному замедлению работы системы и риску аварийного завершения процесса из-за нехватки памяти.
Для промышленных решений всегда используйте только Запросы, выбирая исключительно поле Ссылка.
---
В исходном обсуждении на форуме (сообщение 5) разработчик выгружал результаты запроса во временную таблицу значений (Запрос.Выполнить().Выгрузить()), а затем в цикле получал объект базы данных через метод ПолучитьОбъект() и записывал изменения. Разберем, почему это антипаттерн и как оптимизировать этот код.
Выборка.Следующий() — это позволяет платформе подгружать данные порциями.ОбщегоНазначения.ИмяТаблицыПоСсылке(Ссылка). Это защитит ваш код от ошибок типизации.Проанализируем пример оптимизированного кода пакетного изменения документов с ограничением транзакционной нагрузки:
&НаСервере
Процедура ВыполнитьОптимизированнуюЗапись(ИмяДокумента)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Док.Ссылка КАК Ссылка
|ИЗ
| Документ." + ИмяДокумента + " КАК Док
|ГДЕ
| НЕ Док.ПометкаУдаления";
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Возврат;
КонецЕсли;
Выборка = Результат.Выбрать();
// Счетчик для контроля транзакций
СчетчикДокументов = 0;
МаксимумВТранзакции = 100; // Оптимальный размер порции данных
НачатьТранзакцию();
Пока Выборка.Следующий() Цикл
Попытка
// Получаем объект для записи изменений
ОбъектДок = Выборка.Ссылка.ПолучитьОбъект();
// Выполняем требуемые действия, например, заполняем какой-то реквизит
ОбъектДок.Комментарий = "Обработано автоматически " + ТекущаяДата();
// Записываем документ с отключением лишних проверок для ускорения работы
ОбъектДок.Записать(РежимЗаписиДокумента.Запись);
СчетчикДокументов = СчетчикДокументов + 1;
// Каждые 100 документов фиксируем транзакцию и открываем новую
Если СчетчикДокументов % МаксимумВТранзакции = 0 Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
Исключение
// В случае ошибки отменяем текущую транзакцию, чтобы избежать рассинхронизации данных
ОтменитьТранзакцию();
ЗаписатьЛогОшибок(ОписаниеОшибки());
// Возобновляем транзакционный контекст для продолжения цикла
НачатьТранзакцию();
КонецПопытки;
КонецЦикла;
// Завершаем последнюю открытую транзакцию
Если ТранзакцияАктивна() Тогда
ЗафиксироватьТранзакцию();
КонецЕсли;
КонецПроцедуры
Применение рассмотренных подходов позволит вам создавать масштабируемые, быстрые и интуитивно понятные инструменты для конечных пользователей в любых прикладных решениях на платформе 1С:Предприятие 8.3. Для решения прикладных задач массовой корректировки данных без программирования подойдёт универсальная обработка для пакетного изменения реквизитов объектов.