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

Программист 1С v7.7 Управленческий учет Торговля и дистрибуция
← На главную

При ведении учета в 1С 7.7 нередко возникает задача ограничения максимальной суммы одного документа (например, накладной или реализации для формирования счетов-фактур в рамках законодательных или внутренних лимитов). Если итоговая сумма документа превышает заданное пороговое значение, исходный документ требуется уменьшить до разрешенного предела, а все оставшиеся товарные позиции перенести в один или несколько новых аналогичных документов.

Рассмотрим подробно алгоритм реализации такого механизма, проанализируем частые ошибки при работе с объектом ТаблицаЗначений и разберем готовый код обработки.

Архитектура решения и логика алгоритма

Разберем общую схему решения задачи по шагам:

  1. Анализ документов: обходим список документов-источников, переданных в обработку (например, во входящей таблице значений).
  2. Проверка суммы: если сумма документа меньше или равна максимальному лимиту, документ пропускается без изменений.
  3. Выгрузка табличной части: если сумма превышает порог, выгружаем все строки документа в промежуточный объект ТаблицаЗначений с помощью метода ВыгрузитьТабличнуюЧасть().
  4. Формирование порций: последовательно перебираем строки промежуточной таблицы, накапливая сумму. Как только добавление очередной строки приближает или превышает лимит, формируется новый документ-приемник, в который загружается набранная табличная часть.
  5. Обновление исходного документа: последняя оставшаяся часть строк (или первая порция, укладывающаяся в лимит) записывается обратно в исходный документ с помощью метода ЗагрузитьТабличнуюЧасть().

Существует также альтернативный подход с очередью: исходный документ делится ровно на две части — документ нужного размера и документ с остатком. Ссылка на новый документ с остатком добавляется в конец исходного списка обработки. Когда цикл доходит до него, он при необходимости делится снова. Однако алгоритм с полной предварительной раскладкой строк в памяти является более управляемым и предсказуемым.

Типичные ошибки при работе с таблицами значений в 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. Строка превышает лимит целиком: если одна товарная позиция имеет сумму больше МаксСуммаНакладных, простое перемещение целых строк не решит задачу. В этом случае строку требуется разделить по количеству. Однако разложение на единичные количества (по 1 шт.) резко замедляет работу при тысячах единиц товара. Оптимально вычислять максимально допустимое количество для текущего документа: КоличествоВДок = Цел((МаксСумма - НабраннаяСумма) / Цена), разделяя строку на две части только при необходимости.
  2. Партии и проведение: если исходный документ был проведен, перед модификацией строк его необходимо сделать непроведенным с помощью СделатьНеПроведенным(). В конфигурациях с партионным учетом при дроблении строк рекомендуется очищать реквизит Партия в табличной части, чтобы механизм проведения заново и корректно подобрал партии по ФИФО/ЛИФО для каждого нового документа.
  3. Подчиненные документы: если к исходной накладной уже был привязан подчиненный счет-фактура, после уменьшения суммы накладной его необходимо переформировать или скорректировать с помощью выборки подчиненных документов через ВыбратьПодчиненныеДокументы().
← На главную