Как правильно создать документы по результату запроса в 1С?

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

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

Ограничения платформы 1С и неизбежность цикла

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

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

  1. Вызвать метод создания объекта у соответствующего менеджера документов: Документы.ИмяДокумента.СоздатьДокумент().
  2. Заполнить реквизиты шапки и табличных частей.
  3. Вызвать метод Записать() в необходимом режиме (запись или проведение).

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

Базовый вариант решения

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


Выборка = Запрос.Выполнить().Выбрать();

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

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

Оптимизация 1. Управление явными транзакциями

Выясним причину возможного замедления: если метод Записать() вызывается без открытия явной транзакции, платформа 1С открывает и фиксирует отдельную транзакцию на уровне СУБД для каждого документа. Это приводит к постоянным операциям сброса журнала транзакций на диск (fsync), что сильно замедляет групповую обработку.

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


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

Важно: если создаются сотни или тысячи документов, объединять все операции в одну огромную транзакцию опасно из-за роста блокировок и риска взаимных блокировок (deadlocks). В таких случаях рекомендуется фиксировать транзакцию порциями — например, каждые 50–100 документов.

Оптимизация 2. Корректность заполнения реквизитов и даты

Посмотрим на нюансы заполнения реквизитов:

  1. Заполнение по умолчанию: Функция ЗаполнитьЗначенияСвойств только копирует совпадающие свойства, игнорируя процедуру ОбработкаЗаполнения модуля объекта. Если в документе должны автоматически подставляться валюта, автор или организация по умолчанию, предпочтительнее использовать метод Док.Заполнить(ДанныеЗаполнения).
  2. Дата документа: При вызове СоздатьДокумент() поле Дата содержит пустую дату. Если дата не передается в запросе, ее необходимо обязательно заполнять значением ТекущаяДатаСеанса(), иначе проведение документа может завершиться ошибкой или нарушить хронологию нумерации.
  3. Режим проведения: При неоперативном проведении (например, создание документов прошлым числом) следует явно передавать режим проведения: Док.Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Неоперативный);.

Оптимизация 3. Пакетная загрузка строк на форму

Добавление строк в таблицу формы по одной через Объект.Документы.Добавить() внутри серверного цикла записи объектов создает лишнюю нагрузку. Намного эффективнее накапливать ссылки созданных документов во временной таблице или массиве и по окончании цикла выполнить пакетную загрузку с помощью метода Объект.Документы.Загрузить(ТаблицаСозданных).

Итоговый оптимизированный пример

Соберем все рекомендации в единый надежный алгоритм:


ТаблицаРезультатов = Новый ТаблицаЗначений;
ТаблицаРезультатов.Колонки.Добавить("Ссылка", Новый ОписаниеТипов("ДокументСсылка.УниверсальныйДокумент"));

Выборка = Запрос.Выполнить().Выбрать();

Счетчик = 0;
РазмерПорции = 100;

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

// Единовременная загрузка данных в таблицу формы
Объект.Документы.Загрузить(ТаблицаРезультатов);

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

← На главную