При необходимости массового восстановления движений или перепроведения документов за длительный период разработчики нередко сталкиваются с ситуацией, когда стандартное групповое проведение не учитывает требуемую логику, а ручное написание запроса к сотням видов документов занимает слишком много времени. Проанализируем ситуацию, выясним технические ограничения платформы и разберем по шагам, как автоматизировать эту задачу с помощью программного обхода метаданных.
Попытка вручную составить текст запроса с операторами ОБЪЕДИНИТЬ или ОБЪЕДИНИТЬ ВСЕ для всех существующих документов конфигурации наталкивается на ряд серьезных проблем:
ОБЪЕДИНИТЬ ВСЕ перегружает оптимизатор базы данных. В некоторых случаях СУБД выдает ошибки переполнения стека синтаксического анализатора либо чрезмерно долго строит план выполнения.rphost.Рассмотрим, как сэкономить время и получить список нужных документов программно. В платформе 1С доступна коллекция Метаданные.Документы, которую можно обойти в цикле и автоматически сконструировать запрос с оператором ОБЪЕДИНИТЬ ВСЕ.
Обратим внимание: важно использовать именно ОБЪЕДИНИТЬ ВСЕ, так как обычный оператор ОБЪЕДИНИТЬ выполняет ресурсоемкую операцию устранения дубликатов, которая здесь избыточна.
Посмотрим на пример генератора запроса:
ТекстЗапроса = "";
ПервыйДокумент = Истина;
Для Каждого МДДокумент Из Метаданные.Документы Цикл
// Пропускаем документы, которые не могут проводиться
Если МДДокумент.Проведение = Метаданные.СвойстваОбъектов.Проведение.Запретить Тогда
Продолжить;
КонецЕсли;
Если Не ПервыйДокумент Тогда
ТекстЗапроса = ТекстЗапроса + Символы.ПС + "ОБЪЕДИНИТЬ ВСЕ" + Символы.ПС;
КонецЕсли;
Шаблон =
"ВЫБРАТЬ
| &ИмяМетаданных КАК ИмяВида,
| Т.Ссылка КАК Ссылка,
| Т.Дата КАК Дата,
| Т.МоментВремени КАК МоментВремени
|ИЗ
| Документ.%1 КАК Т
|ГДЕ
| Т.ПометкаУдаления = ЛОЖЬ
| И Т.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания";
ТекстКуска = СтрШаблон(Шаблон, МДДокумент.Имя);
ТекстКуска = СтрЗаменить(ТекстКуска, "&ИмяМетаданных", """" + МДДокумент.Имя + """");
ТекстЗапроса = ТекстЗапроса + ТекстКуска;
ПервыйДокумент = Ложь;
КонецЦикла;
Запрос = Новый Запрос(ТекстЗапроса);
Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);
Запрос.УстановитьПараметр("ДатаОкончания", ДатаОкончания);
Если в базе данных свыше сотни типов документов, сборка одного гигантского запроса может завершиться сбоем на стороне SQL-сервера. Разберем более надежный подход: выполнять запросы небольшими порциями (например, для каждого вида документа отдельно или блоками по 15–20 видов) и выгружать результаты в общую таблицу значений.
Посмотрим на реализацию поочередной выборки с объединением данных в памяти:
ТаблицаВсехДокументов = Новый ТаблицаЗначений;
ТаблицаВсехДокументов.Колонки.Добавить("Ссылка");
ТаблицаВсехДокументов.Колонки.Добавить("Дата");
ТаблицаВсехДокументов.Колонки.Добавить("ИмяВида");
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);
Запрос.УстановитьПараметр("ДатаОкончания", ДатаОкончания);
Для Каждого МДДокумент Из Метаданные.Документы Цикл
Если МДДокумент.Проведение = Метаданные.СвойстваОбъектов.Проведение.Запретить Тогда
Продолжить;
КонецЕсли;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Ссылка КАК Ссылка,
| Т.Дата КАК Дата,
| """ + МДДокумент.Имя + """ КАК ИмяВида
|ИЗ
| Документ." + МДДокумент.Имя + " КАК Т
|ГДЕ
| Т.ПометкаУдаления = ЛОЖЬ
| И Т.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Стр = ТаблицаВсехДокументов.Добавить();
ЗаполнитьЗначенияСвойств(Стр, Выборка);
КонецЦикла;
КонецЦикла;
Проанализируем критически важный нюанс: проведение документов «от последних к первым» (с конца периода к началу) нарушает базовые принципы учета по остаткам. Если расходный документ будет проведен раньше, чем приходный, возникнут отрицательные остатки в регистрах накопления и сработает контроль остатков.
Чтобы проведение прошло без ошибок, необходимо придерживаться следующих правил:
МоментВремени.Для реализации логического порядка назначим каждому виду документа числовой вес приоритета:
// Настройка весов приоритета (чем меньше число, тем раньше проводится документ)
Приоритеты = Новый Соответствие;
Приоритеты.Вставить("ПоступлениеТоваровУслуг", 10);
Приоритеты.Вставить("ПеремещениеТоваров", 20);
Приоритеты.Вставить("РеализацияТоваровУслуг", 30);
Приоритеты.Вставить("СчетФактураВыданный", 40);
ТаблицаВсехДокументов.Колонки.Добавить("Приоритет", Новый ОписаниеТипов("Число"));
Для Каждого Стр Из ТаблицаВсехДокументов Цикл
Вес = Приоритеты[Стр.ИмяВида];
Стр.Приоритет = ?(Вес <> Неопределено, Вес, 100);
КонецЦикла;
// Сортировка: сначала по дате, затем по логическому приоритету
ТаблицаВсехДокументов.Сортировать("Дата Возр, Приоритет Возр");
При выполнении массового проведения исторических документов следует строго использовать неоперативный режим проведения РежимПроведенияДокумента.Неоперативный, чтобы платформа не смещала время документа на текущее системное.
Кроме того, каждый документ необходимо обрабатывать в собственной транзакции с перехватом ошибок через блок Попытка ... Исключение. Если проводить всю выборку в одной общей транзакции, то ошибка проведения всего одного документа отменит все предыдущие результаты и заблокирует таблицы базы данных на длительное время.
Посмотрим на реализацию цикла проведения с обработкой исключений:
Для Каждого СтрокаДокумента Из ТаблицаВсехДокументов Цикл
ДокОбъект = СтрокаДокумента.Ссылка.ПолучитьОбъект();
Если ДокОбъект = Неопределено Тогда
Продолжить;
КонецЕсли;
Попытка
// Неоперативное проведение без смещения времени
ДокОбъект.Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Неоперативный);
Исключение
ИнформацияОбОшибке = ОписаниеОшибки();
ТекстЛога = "Ошибка проведения документа: " + Строка(СтрокаДокумента.Ссылка) + ". Причина: " + ИнформацияОбОшибке;
ЗаписьЖурналаРегистрации(
"ГрупповоеПроведение",
УровеньЖурналаРегистрации.Ошибка,
СтрокаДокумента.Ссылка.Метаданные(),
СтрокаДокумента.Ссылка,
ТекстЛога
);
КонецПопытки;
КонецЦикла;
Такой подход защищает систему от фатальных падений, гарантирует соблюдение хронологии и исключает необходимость вручную собирать запросы для сотен конфигурационных объектов.