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

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

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

Почему ручной запрос с объединением сотен документов неэффективен

Попытка вручную составить текст запроса с операторами ОБЪЕДИНИТЬ или ОБЪЕДИНИТЬ ВСЕ для всех существующих документов конфигурации наталкивается на ряд серьезных проблем:

  1. Трудоемкость поддержки: перечисление сотен таблиц вручную приводит к огромным временным затратам и высокой вероятности ошибок в названиях полей или фильтрах.
  2. Ограничения СУБД: склеивание более сотни таблиц в одном SQL-запросе через ОБЪЕДИНИТЬ ВСЕ перегружает оптимизатор базы данных. В некоторых случаях СУБД выдает ошибки переполнения стека синтаксического анализатора либо чрезмерно долго строит план выполнения.
  3. Избыточный расход ресурсов: выборка сотен тысяч строк в одну огромную результирующую выборку может привести к переполнению оперативной памяти процесса rphost.

Динамическое формирование текста запроса через коллекцию метаданных

Рассмотрим, как сэкономить время и получить список нужных документов программно. В платформе 1С доступна коллекция Метаданные.Документы, которую можно обойти в цикле и автоматически сконструировать запрос с оператором ОБЪЕДИНИТЬ ВСЕ.

Обратим внимание: важно использовать именно ОБЪЕДИНИТЬ ВСЕ, так как обычный оператор ОБЪЕДИНИТЬ выполняет ресурсоемкую операцию устранения дубликатов, которая здесь избыточна.

Посмотрим на пример генератора запроса:


ТекстЗапроса = "";
ПервыйДокумент = Истина;

Для Каждого МДДокумент Из Метаданные.Документы Цикл
    // Пропускаем документы, которые не могут проводиться
    Если МДДокумент.Проведение = Метаданные.СвойстваОбъектов.Проведение.Запретить Тогда
        Продолжить;
    КонецЕсли;

    Если Не ПервыйДокумент Тогда
        ТекстЗапроса = ТекстЗапроса + Символы.ПС + "ОБЪЕДИНИТЬ ВСЕ" + Символы.ПС;
    КонецЕсли;

    Шаблон = 
    "ВЫБРАТЬ
    |	&ИмяМетаданных КАК ИмяВида,
    |	Т.Ссылка КАК Ссылка,
    |	Т.Дата КАК Дата,
    |	Т.МоментВремени КАК МоментВремени
    |ИЗ
    |	Документ.%1 КАК Т
    |ГДЕ
    |	Т.ПометкаУдаления = ЛОЖЬ
    |	И Т.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания";

    ТекстКуска = СтрШаблон(Шаблон, МДДокумент.Имя);
    ТекстКуска = СтрЗаменить(ТекстКуска, "&ИмяМетаданных", """" + МДДокумент.Имя + """");

    ТекстЗапроса = ТекстЗапроса + ТекстКуска;
    ПервыйДокумент = Ложь;
КонецЦикла;

Запрос = Новый Запрос(ТекстЗапроса);
Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);
Запрос.УстановитьПараметр("ДатаОкончания", ДатаОкончания);

Пакетное получение документов для обхода ограничений СУБД

Если в базе данных свыше сотни типов документов, сборка одного гигантского запроса может завершиться сбоем на стороне SQL-сервера. Разберем более надежный подход: выполнять запросы небольшими порциями (например, для каждого вида документа отдельно или блоками по 15–20 видов) и выгружать результаты в общую таблицу значений.

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


ТаблицаВсехДокументов = Новый ТаблицаЗначений;
ТаблицаВсехДокументов.Колонки.Добавить("Ссылка");
ТаблицаВсехДокументов.Колонки.Добавить("Дата");
ТаблицаВсехДокументов.Колонки.Добавить("ИмяВида");

Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);
Запрос.УстановитьПараметр("ДатаОкончания", ДатаОкончания);

Для Каждого МДДокумент Из Метаданные.Документы Цикл
    Если МДДокумент.Проведение = Метаданные.СвойстваОбъектов.Проведение.Запретить Тогда
        Продолжить;
    КонецЕсли;

    Запрос.Текст = 
    "ВЫБРАТЬ
    |	Т.Ссылка КАК Ссылка,
    |	Т.Дата КАК Дата,
    |	""" + МДДокумент.Имя + """ КАК ИмяВида
    |ИЗ
    |	Документ." + МДДокумент.Имя + " КАК Т
    |ГДЕ
    |	Т.ПометкаУдаления = ЛОЖЬ
    |	И Т.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания";

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

Определение правильного порядка проведения

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

Чтобы проведение прошло без ошибок, необходимо придерживаться следующих правил:

  1. Хронологический порядок: документы должны обрабатываться строго от ранних дат к поздним (по возрастанию даты).
  2. Разрешение коллизий в рамках одной секунды: если документы имеют одинаковую секунду, точной позицией выступает МоментВремени.
  3. Приоритеты бизнес-логики: для документов, оформленных на одну дату и время, необходимо соблюдать смысловую очередность (например, сначала «Поступление товаров», затем «Перемещение товаров», далее «Реализация товаров», а в конце — «Счет-фактура выданный»).

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


// Настройка весов приоритета (чем меньше число, тем раньше проводится документ)
Приоритеты = Новый Соответствие;
Приоритеты.Вставить("ПоступлениеТоваровУслуг", 10);
Приоритеты.Вставить("ПеремещениеТоваров", 20);
Приоритеты.Вставить("РеализацияТоваровУслуг", 30);
Приоритеты.Вставить("СчетФактураВыданный", 40);

ТаблицаВсехДокументов.Колонки.Добавить("Приоритет", Новый ОписаниеТипов("Число"));

Для Каждого Стр Из ТаблицаВсехДокументов Цикл
    Вес = Приоритеты[Стр.ИмяВида];
    Стр.Приоритет = ?(Вес <> Неопределено, Вес, 100);
КонецЦикла;

// Сортировка: сначала по дате, затем по логическому приоритету
ТаблицаВсехДокументов.Сортировать("Дата Возр, Приоритет Возр");

Безопасное проведение с изоляцией транзакций

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

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

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


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

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

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

← На главную