Как разбить строки документа на несколько накладных с ограничением по максимальной сумме?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

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

Постановка задачи и базовые сложности

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

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

  1. Строка помещается в остаток лимита целиком: сумма текущей строки добавляется к текущему документу.
  2. Строка превышает остаток лимита или весь лимит документа: такую строку невозможно перенести целиком — её необходимо пропорционально разделить по количеству и сумме на две или более части.

Вариант 1. Разделение строк через массив таблиц значений

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

  1. Предварительное дробление строк, которые сами по себе превышают лимит.
  2. Распределение подготовленных строк по документам с контролем накопительной суммы.

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


// Подготовим исходную таблицу значений
ТаблицаТоваров = ИсходныйДокумент.Товары.Выгрузить();

// Добавим колонку для группировки по документам
ТаблицаТоваров.Колонки.Добавить("НомерПачки", Новый ОписаниеТипов("Число"));

ПределПоСумме = 150000;
ТекущийНомерПачки = 1;
ТекущаяСуммаПачки = 0;

Для Каждого Стр Из ТаблицаТоваров Цикл
    
    // Если добавление строки превышает лимит, переходим к следующей пачке
    Если ТекущаяСуммаПачки > 0 И (ТекущаяСуммаПачки + Стр.Сумма > ПределПоСумме) Тогда
        ТекущийНомерПачки = ТекущийНомерПачки + 1;
        ТекущаяСуммаПачки = 0;
    КонецЕсли;
    
    Стр.НомерПачки = ТекущийНомерПачки;
    ТекущаяСуммаПачки = ТекущаяСуммаПачки + Стр.Сумма;
    
КонецЦикла;

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


// Получаем список уникальных номеров сформированных пачек
ТаблицаПачек = ТаблицаТоваров.Скопировать(, "НомерПачки");
ТаблицаПачек.Свернуть("НомерПачки");

Для Каждого СтрПачки Из ТаблицаПачек Цикл
    Отбор = Новый Структура("НомерПачки", СтрПачки.НомерПачки);
    СтрокиДокумента = ТаблицаТоваров.Скопировать(Отбор);
    
    // Передаем строки в процедуру создания нового документа
    СоздатьНакладнуюПоТаблице(СтрокиДокумента, ИсходныйДокумент);
КонецЦикла;

Вариант 2. Корректное дробление позиций с учетом неделимости единиц

Проанализируем ситуацию, когда строка с товаром имеет сумму, превышающую лимит, либо когда свободный остаток в текущем документе меньше суммы строки, но мы хотим дозаполнить документ оставшимся количеством. В 1С нельзя делить строку исключительно по сумме: товар имеет единицу измерения, цену и ставку НДС.

Разберем по шагам правильный алгоритм дробления строки:

  1. Расчет доступного остатка суммы: вычисляем, сколько рублей не хватает до достижения лимита в текущем документе: ОстатокЛимита = ПределПоСумме - ТекущаяСуммаДокумента.
  2. Расчет количества: определяем целое или допустимое дробное количество товара, которое можно приобрести на остаток лимита. Для штучного товара используем округление в меньшую сторону функцией Цел(): КоличествоДляПереноса = Цел(ОстатокЛимита / СтрокаИсходная.Цена).
  3. Пересчет суммы: вычисляем сумму новой строки как КоличествоДляПереноса * СтрокаИсходная.Цена. При таком подходе цена остается точной, не появляются невалидные копейки и дробные доли штучного товара.
  4. Остаток строки: уменьшаем количество и сумму в исходной строке и переносим остаток в следующий документ.

Посмотрим на пример кода функции разделения строки:


Функция ВыделитьЧастьСтроки(ИсходнаяСтрока, ДоступнаяСумма)
    
    Если ИсходнаяСтрока.Цена = 0 Тогда
        Возврат Неопределено;
    КонецЕсли;
    
    // Для штучного товара берем только целую часть
    КоличествоВДокумент = Цел(ДоступнаяСумма / ИсходнаяСтрока.Цена);
    
    Если КоличествоВДокумент = 0 Тогда
        Возврат Неопределено;
    КонецЕсли;
    
    // Создаем структуру с данными отделенной строки
    НоваяСтрока = Новый Структура;
    НоваяСтрока.Вставить("Номенклатура", ИсходнаяСтрока.Номенклатура);
    НоваяСтрока.Вставить("Количество",   КоличествоВДокумент);
    НоваяСтрока.Вставить("Цена",         ИсходнаяСтрока.Цена);
    НоваяСтрока.Вставить("Сумма",        КоличествоВДокумент * ИсходнаяСтрока.Цена);
    
    // Уменьшаем исходную строку
    ИсходнаяСтрока.Количество = ИсходнаяСтрока.Количество - КоличествоВДокумент;
    ИсходнаяСтрока.Сумма      = ИсходнаяСтрока.Количество * ИсходнаяСтрока.Цена;
    
    Возврат НоваяСтрока;
КонецФункции

Оптимизация упаковки документов (First Fit Decreasing)

Если мы последовательно перебираем строки таблицы, количество итоговых документов может оказаться избыточным: в документах останутся «хвосты» неиспользованного лимита, куда не поместилась следующая крупная позиция. В теории алгоритмов это классическая задача о рюкзаке (Bin Packing Problem).

Чтобы минимизировать число создаваемых накладных, применим эвристику сортировки по убыванию сумм. Достаточно перед распределением выполнить сортировку:


// Сортируем позиции от самых дорогих к более дешевым
ТаблицаТоваров.Сортировать("Сумма Убыв");

В этом случае в первую очередь распределяются наиболее дорогие позиции, а остаток лимита в документах эффективно заполняется мелкими сопутствующими товарами.

Учетные и технические требования при создании документов

Выясним причину возможных ошибок проведения и расхождений учета при разделении накладных:

  1. Расхождения по НДС: при делении суммы строки на несколько частей сумма НДС, вычисленная по отдельным строкам, может разойтись с исходным НДС на 1–2 копейки из-за округления. Необходимо контролировать ставку НДС и при закрытии последней части партии выполнять корректировку копеек.
  2. Маркировка и серии: если номенклатура требует указания серийных номеров или кодов маркировки «Честный Знак», простого математического деления количества недостаточно. При дроблении необходимо также переносить привязанные штрихкоды упаковок и серийные номера в соответствующие подчиненные табличные части.
  3. Использование транзакций: создание нескольких связанных документов должно быть защищено механизмом транзакций платформы. Если проведение третьей накладной вызовет ошибку (например, не хватило остатка по складу), система должна откатить создание всех предыдущих накладных:

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

Обязательно заполняйте реквизит ДокументОснование в новых накладных ссылкой на исходный заказ. Это позволит сохранить целостность структуры подчиненности в типовых конфигурациях и упростит дальнейший аудит цепочки документов.

← На главную