В практике разработчика 1С нередко возникает ситуация, когда документ становится «неподъемным». Это происходит, если табличная часть (ТЧ) содержит десятки или сотни тысяч строк. При попытке проведения такого документа система может выдать ошибку «Out of Memory», завершиться по таймаусту или вызвать критические взаимоблокировки в базе данных. В таких случаях единственным выходом является разделение одного огромного документа на несколько меньших по объему.
Рассмотрим подробно, почему возникают такие проблемы и какими способами можно реализовать программное разбиение документа на части, сохранив при этом целостность данных и корректность учета.
Прежде чем переходить к коду, проанализируем ситуацию. Основные ограничения накладываются не только платформой 1С, но и СУБД (SQL Server, PostgreSQL и др.):
ДокументОбъект платформа считывает все его данные в оперативную память. Если строк слишком много, память сервера может быть исчерпана.Разберем способы решения задачи разбиения документа по шагам.
Если задача стоит разово — например, нужно просто «протолкнуть» один конкретный документ, который не проводится, — можно воспользоваться простым алгоритмом деления по индексу строки. Рассмотрим пример для документа ОприходованиеТоваров.
Проанализируем следующий алгоритм:
КоличествоСтрокДокумента = ИсходныйДокумент.Товары.Количество();
ПоловинаСтрок = Цел(КоличествоСтрокДокумента / 2);
// Создаем первый новый документ
НовДок1 = Документы.ОприходованиеТоваров.СоздатьДокумент();
ЗаполнитьЗначенияСвойств(НовДок1, ИсходныйДокумент, "Организация, Склад, ОтражатьВУправленческомУчете");
НовДок1.Дата = ИсходныйДокумент.Дата;
// Переносим первую половину строк
Для Счетчик = 0 По ПоловинаСтрок - 1 Цикл
ТекСтрока = ИсходныйДокумент.Товары.Получить(Счетчик);
НоваяСтрока = НовДок1.Товары.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, ТекСтрока);
КонецЦикла;
НовДок1.Записать();
// Создаем второй новый документ
НовДок2 = Документы.ОприходованиеТоваров.СоздатьДокумент();
ЗаполнитьЗначенияСвойств(НовДок2, ИсходныйДокумент, "Организация, Склад, ОтражатьВУправленческомУчете");
НовДок2.Дата = ИсходныйДокумент.Дата + 1; // Добавляем секунду, чтобы избежать конфликтов в итогов
// Переносим вторую половину строк
Для Счетчик = ПоловинаСтрок По КоличествоСтрокДокумента - 1 Цикл
ТекСтрока = ИсходныйДокумент.Товары.Получить(Счетчик);
НоваяСтрока = НовДок2.Товары.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, ТекСтрока);
КонецЦикла;
НовДок2.Записать();
Важный нюанс: Обратите внимание на использование метода ЗаполнитьЗначенияСвойств. Это позволяет избежать ручного перечисления всех реквизитов строки (Номенклатура, Количество, Цена и т.д.), что делает код более коротким и надежным.
Более правильный подход — разбивать документ не просто пополам, а на части по фиксированному количеству строк (например, по 10 000 строк в каждой части). Это гарантирует, что каждый новый документ точно уложится в рамки производительности системы.
Разберем логику с использованием счетчика и пересоздания объекта:
ДопустимоеКоличествоСтрок = 10000;
СчетчикСтрок = 0;
НовДокумент = Неопределено;
Для Каждого СтрокаТЧ Из ИсходныйДокументОбъект.Товары Цикл
Если НовДокумент = Неопределено Или СчетчикСтрок >= ДопустимоеКоличествоСтрок Тогда
Если НовДокумент <> Неопределено Тогда
НовДокумент.Записать();
КонецЕсли;
НовДокумент = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
ЗаполнитьЗначенияСвойств(НовДокумент, ИсходныйДокументОбъект, "Дата, Организация, Контрагент, Склад");
СчетчикСтрок = 0;
КонецЕсли;
НоваяСтрока = НовДокумент.Товары.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаТЧ);
СчетчикСтрок = СчетчикСтрок + 1;
КонецЦикла;
Если НовДокумент <> Неопределено Тогда
НовДокумент.Записать();
КонецЕсли;
В этом примере мы проверяем, достигнут ли лимит строк. Если да — записываем текущий документ и создаем новый. В конце цикла обязательно записываем последний созданный объект.
Проанализируем ситуацию: если в документе много реквизитов шапки, много дополнительных табличных частей (например, «Возвратная тара», «Услуги»), то переносить всё вручную через ЗаполнитьЗначенияСвойств трудозатратно. Самый эффективный способ — использовать метод Скопировать().
Рассмотрим шаги этого алгоритма:
ТаблицаЗначений.ЭтотОбъект.Скопировать().ТаблицаЗначений.Посмотрим на программную реализацию:
Процедура РазбитьДокумент(ИсходныйДокументОбъект, СтрокВЧасти)
ТЧ_Товары = ИсходныйДокументОбъект.Товары.Выгрузить();
ВсегоСтрок = ТЧ_Товары.Количество();
ТекущаяСтрока = 0;
НомерЧасти = 1;
Пока ТекущаяСтрока < ВсегоСтрок Цикл
// Создаем полную копию со всеми реквизитами и другими ТЧ
НоваяКопия = ИсходныйДокументОбъект.Скопировать();
НоваяКопия.Дата = ИсходныйДокументОбъект.Дата + НомерЧасти; // Смещение времени
НоваяКопия.Товары.Очистить();
// Отбираем строки для текущей части
Для и = 1 По СтрокВЧасти Цикл
Если ТекущаяСтрока >= ВсегоСтрок Тогда Прервать; КонецЕсли;
СтрокаДанных = ТЧ_Товары.Получить(ТекущаяСтрока);
НоваяСтрокаТЧ = НоваяКопия.Товары.Добавить();
ЗаполнитьЗначенияСвойств(НоваяКопия.Товары[НоваяКопия.Товары.Количество()-1], СтрокаДанных);
ТекущаяСтрока = ТекущаяСтрока + 1;
КонецЦикла;
НоваяКопия.Комментарий = "Часть №" + НомерЧасти + " создана из документа " + ИсходныйДокументОбъект.Номер;
НоваяКопия.Записать(РежимЗаписиДокумента.Запись);
НомерЧасти = НомерЧасти + 1;
КонецЦикла;
// Помечаем оригинал на удаление, так как данные распределены
ИсходныйДокументОбъект.УстановитьПометкуУдаления(Истина);
КонецПроцедуры
Проанализировав предложенные решения, выделим несколько критически важных моментов, которые помогут избежать ошибок при эксплуатации:
1. Уникальность времени документа. При создании нескольких документов одной датой крайне желательно увеличивать время каждого последующего документа на 1 секунду (Дата + 1). Это позволит избежать проблем с последовательностью списания партий в регистрах, если в базе включен партионный учет.
2. Очистка исходного объекта. Если вы не удаляете исходный документ, а оставляете в нем часть строк (например, первую тысячу), не забудьте вызвать Объект.Записать(РежимЗаписиДокумента.ОтменаПроведения) перед удалением лишних строк, чтобы очистить движения в регистрах, а затем провести его заново с сокращенным составом ТЧ.
3. Пересчет итогов. В некоторых типовых конфигурациях (УПП, КА 1.1) суммы в шапке документа (СуммаДокумента) не всегда пересчитываются автоматически при изменении ТЧ программно. После загрузки строк рекомендуется вызвать процедуру пересчета сумм, которая обычно находится в модуле объекта документа или общем модуле (например, ЗаполнитьРеквизитыТабличнойЧасти или РассчитатьИтоги).
4. Использование запросов. Если документ настолько велик, что метод Выгрузить() вызывает ошибку нехватки памяти, фрагменты табличной части можно получать напрямую из базы данных с помощью объекта Запрос, используя условия по НомерСтроки (например, ГДЕ НомерСтроки МЕЖДУ 1 И 10000). Это позволит работать с данными порционно, не загружая всю гигантскую таблицу целиком в память сервера приложений.
Выяснив причины и рассмотрев различные подходы, мы можем сделать вывод, что выбор метода зависит от конкретной задачи: для разовых правок подойдет простой цикл, а для регулярных процессов лучше использовать универсальный метод с копированием объекта и контролем лимита строк.