При ведении учета в 1С 7.7 нередко возникает задача ограничения максимальной суммы одного документа (например, накладной или реализации для формирования счетов-фактур в рамках законодательных или внутренних лимитов). Если итоговая сумма документа превышает заданное пороговое значение, исходный документ требуется уменьшить до разрешенного предела, а все оставшиеся товарные позиции перенести в один или несколько новых аналогичных документов.
Рассмотрим подробно алгоритм реализации такого механизма, проанализируем частые ошибки при работе с объектом ТаблицаЗначений и разберем готовый код обработки.
Разберем общую схему решения задачи по шагам:
ТаблицаЗначений с помощью метода ВыгрузитьТабличнуюЧасть().ЗагрузитьТабличнуюЧасть().Существует также альтернативный подход с очередью: исходный документ делится ровно на две части — документ нужного размера и документ с остатком. Ссылка на новый документ с остатком добавляется в конец исходного списка обработки. Когда цикл доходит до него, он при необходимости делится снова. Однако алгоритм с полной предварительной раскладкой строк в памяти является более управляемым и предсказуемым.
При реализации подобного алгоритма разработчики часто сталкиваются с ошибками индексации и некорректным использованием встроенных методов платформы 1С 7.7:
1. Ошибка «Номер за пределами значения»: возникает, когда индекс строки принудительно передается вручную без создания новой строки в таблице-приемнике либо когда счетчик строк не сбрасывается после очистки таблицы методом УдалитьСтроки().
2. Неправильное использование метода Заполнить(): метод ТаблицаЗначений.Заполнить() предназначен для заполнения ячеек одной таблицы фиксированным значением, а не для копирования диапазона строк из другой таблицы. Для переноса диапазона колонок между строками используется обход по колонкам и методы ПолучитьЗначение() / УстановитьЗначение(), либо метод Выгрузить() в отдельную таблицу.
Посмотрим на пример процедуры, которая выполняет универсальное копирование реквизитов шапки и разбивает строки накладной на несколько документов при превышении порога МаксСуммаНакладных.
// Процедура копирования реквизитов шапки документа
Процедура СкопироватьРеквизитыШапки(ДокИсточ, ДокПриемн)
Для Сч = 1 По Метаданные.ОбщийРеквизитШапки() Цикл
ИмяРекв = Метаданные.ОбщийРеквизитШапки(Сч).Идентификатор;
ДокПриемн.УстановитьАтрибут(ИмяРекв, ДокИсточ.ПолучитьАтрибут(ИмяРекв));
КонецЦикла;
Для Сч = 1 По Метаданные.Документ(ДокИсточ.Вид()).РеквизитШапки() Цикл
ИмяРекв = Метаданные.Документ(ДокИсточ.Вид()).РеквизитШапки(Сч).Идентификатор;
ДокПриемн.УстановитьАтрибут(ИмяРекв, ДокИсточ.ПолучитьАтрибут(ИмяРекв));
КонецЦикла;
КонецПроцедуры
// Основная процедура обработки и разделения документов
Процедура РазбитьДокументыПоЛимиту(ТаблицаДокументов, МаксСуммаНакладных)
ТаблицаДокументов.ВыбратьСтроки();
Пока ТаблицаДокументов.ПолучитьСтроку() = 1 Цикл
ТекущийДок = ТаблицаДокументов.Док;
// Проверяем заполненность и сумму
Если ПустоеЗначение(ТекущийДок) = 1 Тогда
Продолжить;
КонецЕсли;
ТЧ_Исходная = СоздатьОбъект("ТаблицаЗначений");
ТекущийДок.ВыгрузитьТабличнуюЧасть(ТЧ_Исходная);
Если ТЧ_Исходная.Итог("Сумма") <= МаксСуммаНакладных Тогда
Продолжить; // Документ не требует разбиения
КонецЕсли;
// Создаем временную таблицу структуры для порций
ТЧ_Порция = СоздатьОбъект("ТаблицаЗначений");
ТЧ_Исходная.Выгрузить(ТЧ_Порция, 1, 1);
ТЧ_Порция.УдалитьСтроки();
ТекущаяСуммаПорции = 0;
ПервыйДокумент = 1;
НачатьТранзакцию();
ТЧ_Исходная.ВыбратьСтроки();
Пока ТЧ_Исходная.ПолучитьСтроку() = 1 Цикл
СуммаСтроки = ТЧ_Исходная.Сумма;
// Если добавление строки превышает лимит и в порции уже есть данные
Если (ТекущаяСуммаПорции + СуммаСтроки > МаксСуммаНакладных) И (ТЧ_Порция.КоличествоСтрок() > 0) Тогда
Если ПервыйДокумент = 1 Тогда
// Перезаписываем исходный документ первой порцией
ДокОбъект = СоздатьОбъект("Документ." + ТекущийДок.Вид());
ДокОбъект.НайтиДокумент(ТекущийДок);
ДокОбъект.ЗагрузитьТабличнуюЧасть(ТЧ_Порция);
ДокОбъект.Записать();
ПервыйДокумент = 0;
Иначе
// Создаем новый документ для накопившейся порции
НовыйДок = СоздатьОбъект("Документ." + ТекущийДок.Вид());
НовыйДок.Новый();
СкопироватьРеквизитыШапки(ТекущийДок, НовыйДок);
НовыйДок.ЗагрузитьТабличнуюЧасть(ТЧ_Порция);
НовыйДок.Записать();
КонецЕсли;
// Очищаем порцию для следующего набора
ТЧ_Порция.УдалитьСтроки();
ТекущаяСуммаПорции = 0;
КонецЕсли;
// Добавляем текущую строку в таблицу порции
ТЧ_Порция.НоваяСтрока();
Для НомКол = 1 По ТЧ_Исходная.КоличествоКолонок() Цикл
ИмяКол = ТЧ_Исходная.ПолучитьПараметрыКолонки(НомКол);
ТЧ_Порция.УстановитьЗначение(ТЧ_Порция.НомерСтроки, ИмяКол, ТЧ_Исходная.ПолучитьЗначение(ТЧ_Исходная.НомерСтроки, ИмяКол));
КонецЦикла;
ТекущаяСуммаПорции = ТекущаяСуммаПорции + СуммаСтроки;
КонецЦикла;
// Записываем финальный остаток строк
Если ТЧ_Порция.КоличествоСтрок() > 0 Тогда
Если ПервыйДокумент = 1 Тогда
ДокОбъект = СоздатьОбъект("Документ." + ТекущийДок.Вид());
ДокОбъект.НайтиДокумент(ТекущийДок);
ДокОбъект.ЗагрузитьТабличнуюЧасть(ТЧ_Порция);
ДокОбъект.Записать();
Иначе
НовыйДок = СоздатьОбъект("Документ." + ТекущийДок.Вид());
НовыйДок.Новый();
СкопироватьРеквизитыШапки(ТекущийДок, НовыйДок);
НовыйДок.ЗагрузитьТабличнуюЧасть(ТЧ_Порция);
НовыйДок.Записать();
КонецЕсли;
КонецЕсли;
ЗафиксироватьТранзакцию();
КонецЦикла;
КонецПроцедуры
Выясним, какие дополнительные нюансы необходимо учесть при промышленной эксплуатации алгоритма:
МаксСуммаНакладных, простое перемещение целых строк не решит задачу. В этом случае строку требуется разделить по количеству. Однако разложение на единичные количества (по 1 шт.) резко замедляет работу при тысячах единиц товара. Оптимально вычислять максимально допустимое количество для текущего документа: КоличествоВДок = Цел((МаксСумма - НабраннаяСумма) / Цена), разделяя строку на две части только при необходимости.СделатьНеПроведенным(). В конфигурациях с партионным учетом при дроблении строк рекомендуется очищать реквизит Партия в табличной части, чтобы механизм проведения заново и корректно подобрал партии по ФИФО/ЛИФО для каждого нового документа.ВыбратьПодчиненныеДокументы().