Как создать несколько документов на основании одного с разделением по реквизиту табличной части?

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

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

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

Архитектурные риски: почему не стоит создавать документы в «ОбработкеПроведения»

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

  1. Затягивание транзакции и взаимоблокировки (Deadlocks): событие проведения выполняется в неявной транзакции. Создание и проведение новых документов внутри этой транзакции блокирует таблицы регистров и справочников, что при одновременной работе пользователей приводит к конфликтам блокировок.
  2. Дублирование данных: если пользователь перепроведет исходный документ, система повторно создаст новую пачку документов прихода, если не реализован сложный механизм поиска и перезаписи ранее созданных объектов.
  3. Проблема отмены проведения: при распроведении документа-источника созданные им документы останутся проведенными, если отдельно не задействован обработчик ОбработкаУдаленияПроведения.

Оптимальным местом для подобной логики является механизм ввода на основании (событие ОбработкаЗаполнения), создание документов по отдельной кнопке в форме через форму помощника либо фоновое регламентное задание.

Способ 1. Использование запроса с обходом иерархических итогов

Разберем классический подход платформы 1С — получение данных табличной части с группировкой по ключевому реквизиту с помощью секции ИТОГИ ПО. Преимущество запроса заключается в том, что СУБД самостоятельно сортирует и группирует данные, возвращая готовую структуру для пакетного создания документов.

Посмотрим на пример реализации, где мы получаем строки документа, группируем их по полю ТипНоменклатуры и обходим выборку:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ТЧ.ТипНоменклатуры КАК ТипНоменклатуры,
    |	ТЧ.Номенклатура КАК Номенклатура,
    |	ТЧ.Количество КАК Количество
    |ИЗ
    |	Документ.ЗагрузкаНоменклатуры.Номенклатура КАК ТЧ
    |ГДЕ
    |	ТЧ.Ссылка = &Ссылка
    |ИТОГИ ПО
    |	ТипНоменклатуры";

Запрос.УстановитьПараметр("Ссылка", ДокументСсылка);

РезультатЗапроса = Запрос.Выполнить();

// Обход верхнего уровня группировки (по типам номенклатуры)
ВыборкаТип = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

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

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

Способ 2. Обработка табличной части в памяти через методы «Свернуть» и «Скопировать»

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

Разберем алгоритм по шагам:

  1. Выгружаем строки табличной части в коллекцию ТаблицаЗначений.
  2. Создаем копию этой таблицы и сворачиваем ее по группировочному полю ТипНоменклатуры, чтобы получить список уникальных значений.
  3. Для каждого уникального типа номенклатуры отбираем соответствующие строки и загружаем их в новый документ.

Посмотрим на пример кода с использованием метода Скопировать со структурой отбора:


// Получаем исходную таблицу данных
ИсходнаяТЗ = ЭтотОбъект.Номенклатура.Выгрузить();

// Получаем список уникальных типов
СписокТипов = ИсходнаяТЗ.Скопировать();
СписокТипов.Свернуть("ТипНоменклатуры");

// Для ускорения отбора больших объемов данных проиндексируем исходную таблицу
ИсходнаяТЗ.Индексы.Добавить("ТипНоменклатуры");

Отбор = Новый Структура("ТипНоменклатуры");

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

Оптимизация производительности при больших объемах

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

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

Выбор между запросом и обработкой в памяти зависит от контекста задачи: если данные уже есть в объекте, проще и быстрее использовать методы Свернуть и Скопировать. Если же требуется обрабатывать уже записанный в информационную базу документ из внешней обработки или регламентного задания, предпочтительнее использовать запрос с конструкцией ИТОГИ ПО.

← На главную