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

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

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

Вариант 1. Построчный перенос с контролем нарастающей суммы

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

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


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

Если НовДок.КоличествоСтрок() > 0 Тогда
    НовДок.Записать();
КонецЕсли;

Вариант 2. Разделение через табличную часть и срезы строк

Рассмотрим более компактный подход, при котором используются встроенные методы работы с коллекциями строк ВыгрузитьТабличнуюЧасть() и ЗагрузитьТабличнуюЧасть(). Этот способ удобен, когда строки табличной части переносятся целыми блоками.

Посмотрим на пример рекурсивного или циклического выделения пачек строк:


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

// Основной цикл разделения
Пока Док.Итог("Сумма") > ЛимитСуммы Цикл
    Если ВыделитьЧастьДокумента(Док, ЛимитСуммы) = 0 Тогда
        // Отдельная строка превышает лимит либо деление завершено
        Прервать;
    КонецЕсли;
КонецЦикла;
Док.Записать();

Копирование реквизитов шапки через метаданные

Чтобы не перечислять вручную все реквизиты документа (договор, склад, контрагент, тип цен и т.д.), воспользуемся объектом Метаданные для автоматического переноса значений шапки:


Процедура СкопироватьРеквизитыШапки(ДокИсходный, ДокРезультат)
    Перем НомерАтр, РеквизитИд, РеквизитЗн;
    
    // Копирование общих реквизитов
    Для НомерАтр = 1 По Метаданные.ОбщийРеквизитДокумента() Цикл
        РеквизитИд = Метаданные.ОбщийРеквизитДокумента(НомерАтр).Идентификатор;
        РеквизитЗн = ДокИсходный.ПолучитьАтрибут(РеквизитИд);
        ДокРезультат.УстановитьАтрибут(РеквизитИд, РеквизитЗн);
    КонецЦикла;
    
    // Копирование собственных реквизитов шапки
    Для НомерАтр = 1 По Метаданные.Документ(ДокИсходный.Вид()).РеквизитШапки() Цикл
        РеквизитИд = Метаданные.Документ(ДокИсходный.Вид()).РеквизитШапки(НомерАтр).Идентификатор;
        РеквизитЗн = ДокИсходный.ПолучитьАтрибут(РеквизитИд);
        ДокРезультат.УстановитьАтрибут(РеквизитИд, РеквизитЗн);
    КонецЦикла;
    
    // Служебные реквизиты
    ДокРезультат.ДатаДок = ДокИсходный.ДатаДок;
КонецПроцедуры

Важные правила при реализации алгоритма

  1. Снятие с проведения перед изменением: если исходный документ уже проведен, перед изменением состава его строк необходимо вызвать метод СделатьНеПроведенным(), иначе возникнут расхождения регистров и табличной части.
  2. Контроль неделимых позиций: если цена единицы товара сама по себе больше установленного лимита, такой товар невозможно разделить стандартным способом без дробления количества или снижения цены. Такие позиции следует оставлять в исходном документе либо обрабатывать отдельно.
  3. Использование транзакций: операции группового создания документов оборачивайте в НачатьТранзакцию() и ЗафиксироватьТранзакцию(), чтобы при ошибке записи одного из созданных документов база не осталась в полуразобранном состоянии.
  4. Хронология и время: для корректного партионного учета новым документам задавайте время исходного документа со смещением через УстановитьВремя().
← На главную